la-machina-engine 0.17.0 → 0.19.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/dist/index.cjs CHANGED
@@ -3986,6 +3986,29 @@ var RunContext = class {
3986
3986
  });
3987
3987
  this.episodes?.logTurn(this.turnCount, "user", text2);
3988
3988
  }
3989
+ /**
3990
+ * Plan 051 — seed a prior chat turn into the run's message stack
3991
+ * before the current task. Writes a normal `user` / `assistant`
3992
+ * transcript entry so resume + inspect + compaction see the same
3993
+ * conversation the model saw, but intentionally:
3994
+ * - does NOT increment `turnCount` (a turn is a model response
3995
+ * this run produced, not historical context)
3996
+ * - does NOT log to `episodes` (avoid cross-run memory noise)
3997
+ *
3998
+ * Caller is responsible for validating role + content shape; this
3999
+ * helper just persists what it's given.
4000
+ */
4001
+ async seedInitialMessage(role, text2) {
4002
+ const content = [{ type: "text", text: text2 }];
4003
+ this.messages.push({ role, content });
4004
+ await this.writeEntry({
4005
+ type: role,
4006
+ uuid: this.nextUuid(),
4007
+ parentUuid: this.lastUuid,
4008
+ ts: this.now(),
4009
+ message: { role, content }
4010
+ });
4011
+ }
3989
4012
  async addAssistantMessage(content) {
3990
4013
  this.messages.push({ role: "assistant", content });
3991
4014
  await this.writeEntry({
@@ -8041,196 +8064,6 @@ function extractDescription(content) {
8041
8064
  return headingText;
8042
8065
  }
8043
8066
 
8044
- // src/prompts/sections/base.ts
8045
- init_cjs_shims();
8046
- function getBaseSection() {
8047
- return `# System
8048
-
8049
- You are an AI assistant running inside la-machina-engine. You complete tasks by using the tools available to you. Your output goes to a programmatic caller (not a human terminal), so focus on correctness and completeness.
8050
-
8051
- IMPORTANT: Assist with authorized security testing, defensive security, CTF challenges, and educational contexts. Refuse requests for destructive techniques, DoS attacks, mass targeting, supply chain compromise, or detection evasion for malicious purposes.
8052
- IMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident they are correct. You may use URLs provided in the task or discovered via tools.`;
8053
- }
8054
-
8055
- // src/prompts/sections/doingTasks.ts
8056
- init_cjs_shims();
8057
- function getDoingTasksSection() {
8058
- return `# Doing tasks
8059
-
8060
- - The caller will request you to perform tasks \u2014 solving bugs, adding features, refactoring, analyzing data, research, and more.
8061
- - You are highly capable and often allow callers to complete ambitious tasks that would otherwise be too complex or take too long. You should defer to the caller's judgement about whether a task is too large to attempt.
8062
- - If you notice the request is based on a misconception, or spot a bug adjacent to what was asked about, say so. You're a collaborator, not just an executor \u2014 callers benefit from your judgment, not just your compliance.
8063
- - In general, do not propose changes to code you haven't read. If asked about or to modify a file, read it first. Understand existing code before suggesting modifications.
8064
- - Do not create files unless they're absolutely necessary for achieving your goal. Generally prefer editing an existing file to creating a new one, as this prevents file bloat and builds on existing work more effectively.
8065
- - Avoid giving time estimates or predictions for how long tasks will take. Focus on what needs to be done, not how long it might take.
8066
- - If an approach fails, diagnose why before switching tactics \u2014 read the error, check your assumptions, try a focused fix. Don't retry the identical action blindly, but don't abandon a viable approach after a single failure either. Escalate only when you're genuinely stuck after investigation, not as a first response to friction.
8067
- - Be careful not to introduce security vulnerabilities such as command injection, XSS, SQL injection, and other OWASP top 10 vulnerabilities. If you notice that you wrote insecure code, immediately fix it. Prioritize writing safe, secure, and correct code.
8068
- - Don't add features, refactor code, or make "improvements" beyond what was asked. A bug fix doesn't need surrounding code cleaned up. A simple feature doesn't need extra configurability. Don't add docstrings, comments, or type annotations to code you didn't change. Only add comments where the logic isn't self-evident.
8069
- - Don't add error handling, fallbacks, or validation for scenarios that can't happen. Trust internal code and framework guarantees. Only validate at system boundaries (user input, external APIs). Don't use feature flags or backwards-compatibility shims when you can just change the code.
8070
- - Don't create helpers, utilities, or abstractions for one-time operations. Don't design for hypothetical future requirements. The right amount of complexity is what the task actually requires \u2014 no speculative abstractions, but no half-finished implementations either. Three similar lines of code is better than a premature abstraction.
8071
- - Default to writing no comments. Only add one when the WHY is non-obvious: a hidden constraint, a subtle invariant, a workaround for a specific bug, behavior that would surprise a reader. If removing the comment wouldn't confuse a future reader, don't write it.
8072
- - Don't explain WHAT the code does, since well-named identifiers already do that. Don't reference the current task, fix, or callers ("used by X", "added for the Y flow", "handles the case from issue #123"), since those belong in the commit message and rot as the codebase evolves.
8073
- - Don't remove existing comments unless you're removing the code they describe or you know they're wrong. A comment that looks pointless to you may encode a constraint or a lesson from a past bug that isn't visible in the current diff.
8074
- - Before reporting a task complete, verify it actually works: run the test, execute the script, check the output. Minimum complexity means no gold-plating, not skipping the finish line. If you can't verify (no test exists, can't run the code), say so explicitly rather than claiming success.
8075
- - Report outcomes faithfully: if tests fail, say so with the relevant output; if you did not run a verification step, say that rather than implying it succeeded. Never claim "all tests pass" when output shows failures, never suppress or simplify failing checks to manufacture a green result, and never characterize incomplete or broken work as done. Equally, when a check did pass or a task is complete, state it plainly \u2014 do not hedge confirmed results with unnecessary disclaimers, downgrade finished work to "partial," or re-verify things you already checked. The goal is an accurate report, not a defensive one.`;
8076
- }
8077
-
8078
- // src/prompts/sections/actions.ts
8079
- init_cjs_shims();
8080
- function getActionsSection() {
8081
- return `# Executing actions with care
8082
-
8083
- Carefully consider the reversibility and blast radius of actions. Generally you can freely take local, reversible actions like editing files or running tests. But for actions that are hard to reverse, affect shared systems beyond your local environment, or could otherwise be risky or destructive, check with the caller before proceeding. The cost of pausing to confirm is low, while the cost of an unwanted action (lost work, unintended messages sent, deleted branches) can be very high. For actions like these, consider the context, the action, and caller instructions, and by default transparently communicate the action and ask for confirmation before proceeding. This default can be changed by caller instructions \u2014 if explicitly asked to operate more autonomously, then you may proceed without confirmation, but still attend to the risks and consequences when taking actions.
8084
-
8085
- Examples of the kind of risky actions that warrant confirmation:
8086
- - Destructive operations: deleting files/branches, dropping database tables, killing processes, rm -rf, overwriting uncommitted changes
8087
- - Hard-to-reverse operations: force-pushing (can overwrite upstream), git reset --hard, amending published commits, removing or downgrading packages/dependencies, modifying CI/CD pipelines
8088
- - Actions visible to others or that affect shared state: pushing code, creating/closing/commenting on PRs or issues, sending messages (Slack, email, GitHub), posting to external services, modifying shared infrastructure or permissions
8089
- - Uploading content to third-party web tools (diagram renderers, pastebins, gists) publishes it \u2014 consider whether it could be sensitive before sending, since it may be cached or indexed even if later deleted.
8090
-
8091
- When you encounter an obstacle, do not use destructive actions as a shortcut to simply make it go away. For instance, try to identify root causes and fix underlying issues rather than bypassing safety checks (e.g. --no-verify). If you discover unexpected state like unfamiliar files, branches, or configuration, investigate before deleting or overwriting, as it may represent in-progress work. For example, typically resolve merge conflicts rather than discarding changes; similarly, if a lock file exists, investigate what process holds it rather than deleting it. In short: only take risky actions carefully, and when in doubt, ask before acting. Follow both the spirit and letter of these instructions \u2014 measure twice, cut once.`;
8092
- }
8093
-
8094
- // src/prompts/sections/usingTools.ts
8095
- init_cjs_shims();
8096
- function getUsingToolsSection(options) {
8097
- const has = (name) => options.registeredToolNames.has(name);
8098
- const items = [];
8099
- items.push(
8100
- `Do NOT use Bash to run commands when a relevant dedicated tool is provided. Using dedicated tools produces clearer, more reviewable output. This is CRITICAL:`
8101
- );
8102
- if (has("Read")) items.push(` - To read files use Read instead of cat, head, tail, or sed`);
8103
- if (has("Edit")) items.push(` - To edit files use Edit instead of sed or awk`);
8104
- if (has("Write"))
8105
- items.push(` - To create files use Write instead of cat with heredoc or echo redirection`);
8106
- if (has("Glob")) items.push(` - To search for files use Glob instead of find or ls`);
8107
- if (has("Grep")) items.push(` - To search the content of files, use Grep instead of grep or rg`);
8108
- items.push(
8109
- ` - Reserve using Bash exclusively for system commands and terminal operations that require shell execution. If you are unsure and there is a relevant dedicated tool, default to using the dedicated tool and only fallback on Bash if it is absolutely necessary.`
8110
- );
8111
- items.push(
8112
- `You can call multiple tools in a single response. If you intend to call multiple tools and there are no dependencies between them, make all independent tool calls in parallel. Maximize use of parallel tool calls where possible to increase efficiency. However, if some tool calls depend on previous calls to inform dependent values, do NOT call these tools in parallel and instead call them sequentially.`
8113
- );
8114
- if (has("Agent")) {
8115
- items.push(
8116
- `Use the Agent tool with specialized agents when the task at hand matches the agent's description. Subagents are valuable for parallelizing independent queries or for protecting the main context window from excessive results, but should not be used excessively when not needed. Importantly, avoid duplicating work that subagents are already doing \u2014 if you delegate research to a subagent, do not also perform the same searches yourself.`
8117
- );
8118
- if (has("Glob") || has("Grep")) {
8119
- items.push(
8120
- `For simple, directed codebase searches (e.g. for a specific file/class/function) use Glob or Grep directly.`
8121
- );
8122
- }
8123
- }
8124
- if (has("SkillPage")) {
8125
- items.push(
8126
- `Skills are surfaced in the system prompt. Use the SkillPage tool to load specific pages from multi-page skills when you need detailed instructions.`
8127
- );
8128
- }
8129
- return `# Using your tools
8130
-
8131
- ${items.map((i) => ` - ${i}`).join("\n")}`;
8132
- }
8133
-
8134
- // src/prompts/sections/toneAndStyle.ts
8135
- init_cjs_shims();
8136
- function getToneAndStyleSection() {
8137
- return `# Tone and style
8138
-
8139
- - Only use emojis if the caller explicitly requests it. Avoid using emojis in all output unless asked.
8140
- - Your responses should be concise and direct. Lead with the answer or action, not the reasoning.
8141
- - When referencing specific functions or pieces of code include the pattern file_path:line_number.
8142
- - When referencing GitHub issues or pull requests, use the owner/repo#123 format.
8143
- - Do not use a colon before tool calls. Your tool calls may not be shown directly in the output, so text like "Let me read the file:" followed by a read tool call should just be "Let me read the file." with a period.
8144
-
8145
- # Output efficiency
8146
-
8147
- Go straight to the point. Try the simplest approach first without going in circles. Do not overdo it. Be extra concise.
8148
-
8149
- Keep your text output brief and direct. Lead with the answer or action, not the reasoning. Skip filler words, preamble, and unnecessary transitions. Do not restate the task \u2014 just do it. When explaining, include only what is necessary.
8150
-
8151
- Focus text output on:
8152
- - Decisions that need input
8153
- - High-level status updates at natural milestones
8154
- - Errors or blockers that change the plan
8155
-
8156
- If you can say it in one sentence, don't use three. Prefer short, direct sentences over long explanations.`;
8157
- }
8158
-
8159
- // src/prompts/sections/environment.ts
8160
- init_cjs_shims();
8161
- async function getEnvironmentSection(options) {
8162
- let platform = "unknown";
8163
- let osRelease = "";
8164
- try {
8165
- const os = await import("os");
8166
- platform = os.platform();
8167
- osRelease = os.release();
8168
- } catch {
8169
- platform = typeof navigator !== "undefined" ? "worker" : "unknown";
8170
- }
8171
- const shell = (typeof process !== "undefined" ? process.env?.SHELL : void 0) ?? (platform === "win32" ? "cmd.exe" : "/bin/sh");
8172
- const osVersion = `${platform}${osRelease ? " " + osRelease : ""}`;
8173
- const cwd = options.cwd ?? (typeof process !== "undefined" && process.cwd ? process.cwd() : "/");
8174
- const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
8175
- const lines = [
8176
- "# Environment",
8177
- "",
8178
- `- Platform: ${platform}`,
8179
- `- Shell: ${shell}`,
8180
- `- OS Version: ${osVersion}`,
8181
- `- Working directory: ${cwd}`,
8182
- `- Model: ${options.modelId} (provider: ${options.provider})`,
8183
- `- Current date: ${date}`
8184
- ];
8185
- if (canSpawnProcesses()) {
8186
- const git = await getGitContext(cwd);
8187
- if (git) {
8188
- lines.push("");
8189
- lines.push("## Git");
8190
- if (git.branch) lines.push(`- Branch: ${git.branch}`);
8191
- if (git.isRepo) lines.push(`- Is git repo: true`);
8192
- if (git.status) lines.push(`- Status:
8193
- ${git.status}`);
8194
- if (git.recentCommits) lines.push(`- Recent commits:
8195
- ${git.recentCommits}`);
8196
- }
8197
- }
8198
- return lines.join("\n");
8199
- }
8200
- async function getGitContext(cwd) {
8201
- try {
8202
- const { execSync } = await import("child_process");
8203
- const opts = {
8204
- cwd,
8205
- stdio: ["ignore", "pipe", "ignore"],
8206
- timeout: 5e3
8207
- };
8208
- try {
8209
- execSync("git rev-parse --is-inside-work-tree", opts);
8210
- } catch {
8211
- return null;
8212
- }
8213
- const branch = tryExec(execSync, "git branch --show-current", opts);
8214
- const status = tryExec(execSync, "git status --short", opts, 2e3);
8215
- const recentCommits = tryExec(execSync, "git log --oneline -5", opts, 2e3);
8216
- const ctx = { isRepo: true };
8217
- if (branch) ctx.branch = branch;
8218
- if (status) ctx.status = status;
8219
- if (recentCommits) ctx.recentCommits = recentCommits;
8220
- return ctx;
8221
- } catch {
8222
- return null;
8223
- }
8224
- }
8225
- function tryExec(execSync, cmd, opts, maxChars = 1e3) {
8226
- try {
8227
- const out = execSync(cmd, opts).toString("utf-8").trim();
8228
- return out.length > maxChars ? out.slice(0, maxChars) + "\n...(truncated)" : out;
8229
- } catch {
8230
- return "";
8231
- }
8232
- }
8233
-
8234
8067
  // src/prompts/sections/mcp.ts
8235
8068
  init_cjs_shims();
8236
8069
  function getMcpSection(options) {
@@ -8316,14 +8149,21 @@ function getApiServicesSection(opts) {
8316
8149
  lines.push("");
8317
8150
  }
8318
8151
  } else {
8319
- lines.push(
8320
- "Configured external HTTP APIs. Use `ApiCall` to invoke, but first call `DescribeService(service)` to fetch that service's endpoint catalog. Auth is injected automatically."
8321
- );
8152
+ const hasDescribe = opts.hasDescribeService !== false;
8153
+ if (hasDescribe) {
8154
+ lines.push(
8155
+ "Configured external HTTP APIs. Use `ApiCall` to invoke, but first call `DescribeService(service)` to fetch that service's endpoint catalog. Auth is injected automatically."
8156
+ );
8157
+ } else {
8158
+ lines.push(
8159
+ "Configured external HTTP APIs. Use `ApiCall` to invoke. Auth is injected automatically."
8160
+ );
8161
+ }
8322
8162
  lines.push("");
8323
8163
  appendStrictApiToolRules(
8324
8164
  lines,
8325
8165
  /* hasLazy */
8326
- true
8166
+ hasDescribe
8327
8167
  );
8328
8168
  for (const svc of withEndpoints) {
8329
8169
  const count = svc.endpoints.length;
@@ -8378,33 +8218,20 @@ function resolveEffectiveMode(services, requested, threshold) {
8378
8218
  // src/prompts/systemPrompt.ts
8379
8219
  async function buildSystemPrompt(options) {
8380
8220
  const sections = [];
8221
+ const visibleToolNames = options.visibleToolNames ?? /* @__PURE__ */ new Set();
8381
8222
  if (options.coordinatorMode) {
8382
- } else if (options.staticBase !== void 0 && options.staticBase.length > 0) {
8223
+ } else if (options.staticBase !== void 0 && options.staticBase.trim().length > 0) {
8383
8224
  sections.push(options.staticBase);
8384
- } else {
8385
- sections.push(getBaseSection());
8386
- sections.push(getDoingTasksSection());
8387
- sections.push(getActionsSection());
8388
- if (options.registeredToolNames !== void 0 && options.registeredToolNames.size > 0) {
8389
- sections.push(getUsingToolsSection({ registeredToolNames: options.registeredToolNames }));
8390
- }
8391
- sections.push(getToneAndStyleSection());
8392
- }
8393
- sections.push(
8394
- await getEnvironmentSection({
8395
- modelId: options.modelId ?? "unknown",
8396
- provider: options.provider ?? "unknown",
8397
- cwd: options.cwd
8398
- })
8399
- );
8225
+ }
8400
8226
  if (options.mcpTools !== void 0 && options.mcpTools.length > 0) {
8401
8227
  const mcpSection = getMcpSection({ mcpTools: options.mcpTools });
8402
8228
  if (mcpSection !== null) sections.push(mcpSection);
8403
8229
  }
8404
- if (options.apiServices !== void 0 && options.apiServices.length > 0) {
8230
+ if (options.apiServices !== void 0 && options.apiServices.length > 0 && visibleToolNames.has("ApiCall")) {
8405
8231
  const apiSection = getApiServicesSection({
8406
8232
  services: options.apiServices,
8407
8233
  mode: options.apiCatalogMode ?? "lazy",
8234
+ hasDescribeService: visibleToolNames.has("DescribeService"),
8408
8235
  ...options.apiLazyTokenThreshold !== void 0 ? { lazyTokenThreshold: options.apiLazyTokenThreshold } : {}
8409
8236
  });
8410
8237
  if (apiSection !== null) sections.push(apiSection);
@@ -8425,7 +8252,7 @@ ${rules}`);
8425
8252
  ${lessons}`);
8426
8253
  }
8427
8254
  const effectiveSkillList = options.skillList !== void 0 ? options.skillList : options.skillsAutoload ? await collectSkills(options.storage, options.skillsDir ?? "skills") : void 0;
8428
- if (effectiveSkillList !== void 0 && effectiveSkillList.length > 0) {
8255
+ if (effectiveSkillList !== void 0 && effectiveSkillList.length > 0 && visibleToolNames.has("SkillPage")) {
8429
8256
  const lines = ["# Skills"];
8430
8257
  for (const skill of effectiveSkillList) {
8431
8258
  lines.push(`- ${skill.name}: ${skill.description}`);
@@ -10847,6 +10674,18 @@ function scrubRunOptions(opts) {
10847
10674
  if (opts.tools !== void 0) out.tools = [...opts.tools];
10848
10675
  if (opts.toolChoice !== void 0) out.toolChoice = opts.toolChoice;
10849
10676
  if (opts.tokenBudget !== void 0) out.tokenBudget = opts.tokenBudget;
10677
+ if (opts.systemPromptBase !== void 0 && opts.systemPromptBase.length > 0) {
10678
+ out.systemPromptBase = {
10679
+ present: true,
10680
+ chars: opts.systemPromptBase.length
10681
+ };
10682
+ }
10683
+ if (opts.systemPromptAppend !== void 0 && opts.systemPromptAppend.length > 0) {
10684
+ out.systemPromptAppend = {
10685
+ present: true,
10686
+ chars: opts.systemPromptAppend.length
10687
+ };
10688
+ }
10850
10689
  if (opts.knowledge !== void 0) {
10851
10690
  const k = {};
10852
10691
  if (opts.knowledge.folders !== void 0) k.folders = [...opts.knowledge.folders];
@@ -10879,6 +10718,24 @@ function scrubRunOptions(opts) {
10879
10718
  }
10880
10719
  if (opts.compaction !== void 0) out.compaction = opts.compaction;
10881
10720
  if (opts.context !== void 0) out.context = { ...opts.context };
10721
+ if (opts.initialMessages !== void 0 && Array.isArray(opts.initialMessages)) {
10722
+ const roles = [];
10723
+ let totalChars = 0;
10724
+ for (const m of opts.initialMessages) {
10725
+ if (m === null || typeof m !== "object") continue;
10726
+ const role = m.role;
10727
+ const content = m.content;
10728
+ if (role !== "user" && role !== "assistant") continue;
10729
+ if (typeof content !== "string") continue;
10730
+ roles.push(role);
10731
+ totalChars += content.length;
10732
+ }
10733
+ out.initialMessages = {
10734
+ count: roles.length,
10735
+ roles,
10736
+ totalChars
10737
+ };
10738
+ }
10882
10739
  return out;
10883
10740
  }
10884
10741
  function serializeOutputSchema(schema) {
@@ -10954,6 +10811,14 @@ function rebuildMessagesFromEntries(entries) {
10954
10811
  return messages;
10955
10812
  }
10956
10813
 
10814
+ // src/engine/types.ts
10815
+ init_cjs_shims();
10816
+ var INITIAL_MESSAGES_LIMITS = {
10817
+ maxMessages: 100,
10818
+ maxTotalChars: 32e3,
10819
+ maxCharsPerMessage: 8e3
10820
+ };
10821
+
10957
10822
  // src/engine/response.ts
10958
10823
  init_cjs_shims();
10959
10824
  function toResponse(result, extra) {
@@ -11344,60 +11209,36 @@ var Engine = class {
11344
11209
  const memory = createSmartMemory({ storage, config: this.config.memory });
11345
11210
  const agents = await this.resolveAgents(storage);
11346
11211
  const mcpTools = await this.mcpManager.getTools();
11347
- const toolNameSet = this.collectToolNames(mcpTools);
11348
11212
  const coordinatorBase = isCoordinatorMode(this.config) ? getCoordinatorBasePrompt() : void 0;
11349
11213
  const skillSource = this.resolveSkillSource(options.skills, storage);
11350
11214
  const skillList = skillSource !== void 0 ? await skillSource.list() : void 0;
11351
11215
  const apiConfig = this.resolveApiConfig(options.api);
11352
11216
  const offloadConfig = this.resolveOffloadConfig(options.compaction?.toolResultOffload);
11353
11217
  const knowledgeRuntime = this.resolveKnowledgeRuntime(options.knowledge, storage);
11354
- let systemPrompt = await buildSystemPrompt({
11355
- ...coordinatorBase !== void 0 ? { base: coordinatorBase } : {},
11356
- ...options.systemPromptBase !== void 0 ? { staticBase: options.systemPromptBase } : {},
11357
- ...options.systemPromptAppend !== void 0 && options.systemPromptAppend.length > 0 ? { platformAppend: options.systemPromptAppend } : {},
11358
- memory,
11359
- storage,
11360
- // When an override was supplied, skip the legacy disk-scan path.
11361
- skillsAutoload: options.skills !== void 0 ? false : this.config.skills.autoload,
11362
- ...this.config.skills.path !== void 0 ? { skillsDir: this.config.skills.path } : {},
11363
- ...skillList !== void 0 ? { skillList } : {},
11364
- modelId: this.config.model.modelId,
11365
- provider: this.config.model.provider,
11366
- registeredToolNames: toolNameSet,
11367
- mcpTools,
11368
- coordinatorMode: isCoordinatorMode(this.config),
11369
- // Plan 047 — render API services catalog (lazy by default).
11370
- ...apiConfig !== void 0 && apiConfig.services.length > 0 ? {
11371
- apiServices: apiConfig.services,
11372
- apiCatalogMode: apiConfig.mode ?? "lazy",
11373
- ...apiConfig.lazyTokenThreshold !== void 0 ? { apiLazyTokenThreshold: apiConfig.lazyTokenThreshold } : {}
11374
- } : {}
11375
- });
11376
- if (options.outputFormat === "json") {
11377
- systemPrompt += "\n\n" + buildSchemaPrompt(options.outputSchema);
11378
- }
11379
11218
  const gate = this.resolveGate();
11380
11219
  const inspect = this.buildInspectWriter(storage.workspace, logPath);
11381
- const registry = buildToolRegistry({
11382
- config: this.config,
11220
+ const { systemPrompt: assembledPrompt, registry } = await this.buildPromptAndRegistry({
11221
+ runOptions: options,
11222
+ coordinatorBase,
11383
11223
  storage,
11384
11224
  client,
11385
- parentLogPath: logPath,
11386
- parentAgentId: null,
11225
+ logPath,
11387
11226
  subagentRegistry,
11388
- system: systemPrompt,
11389
11227
  agents,
11390
11228
  mcpTools,
11391
11229
  memory,
11392
11230
  inspect,
11393
- ...this.config.hooks.propagateGateToSubagents === true && gate !== void 0 ? { subagentGate: gate } : {},
11394
- ...skillSource !== void 0 ? { skillSource } : {},
11395
- ...apiConfig !== void 0 ? { apiConfig } : {},
11396
- ...offloadConfig !== void 0 ? { toolResultOffload: offloadConfig } : {},
11397
- ...knowledgeRuntime !== void 0 ? { knowledge: knowledgeRuntime } : {},
11398
- ...this.internals.fetch !== void 0 ? { fetch: this.internals.fetch } : {}
11231
+ gate,
11232
+ skillSource,
11233
+ skillList,
11234
+ apiConfig,
11235
+ offloadConfig,
11236
+ knowledgeRuntime
11399
11237
  });
11400
- applyRunToolFilter(registry, options);
11238
+ let systemPrompt = assembledPrompt;
11239
+ if (options.outputFormat === "json") {
11240
+ systemPrompt += "\n\n" + buildSchemaPrompt(options.outputSchema);
11241
+ }
11401
11242
  await inspect.writeStartSnapshot({
11402
11243
  systemPrompt,
11403
11244
  tools: this.snapshotTools(registry, mcpTools),
@@ -11456,6 +11297,23 @@ var Engine = class {
11456
11297
  const runTimeout = this.startRunTimeout();
11457
11298
  try {
11458
11299
  await writer.setStatus("running");
11300
+ if (options.initialMessages !== void 0) {
11301
+ const validation = validateInitialMessages(options.initialMessages);
11302
+ if (!validation.ok) {
11303
+ await writer.setStatus("failed");
11304
+ return {
11305
+ runId,
11306
+ status: "failed",
11307
+ data: null,
11308
+ meta: { nodeId: options.nodeId, durationMs: Date.now() - startTime },
11309
+ errors: [{ code: "ERR_INVALID_INITIAL_MESSAGES", message: validation.message }],
11310
+ timestamp: Date.now()
11311
+ };
11312
+ }
11313
+ for (const m of validation.messages) {
11314
+ await ctx.seedInitialMessage(m.role, m.content);
11315
+ }
11316
+ }
11459
11317
  await dispatchHooks(this.config.hooks.preRun, {
11460
11318
  runId,
11461
11319
  nodeId: options.nodeId,
@@ -11549,68 +11407,55 @@ var Engine = class {
11549
11407
  const memory = createSmartMemory({ storage, config: this.config.memory });
11550
11408
  const agents = await this.resolveAgents(storage);
11551
11409
  const mcpTools = await this.mcpManager.getTools();
11552
- const toolNameSet = this.collectToolNames(mcpTools);
11553
11410
  const coordinatorBase = isCoordinatorMode(this.config) ? getCoordinatorBasePrompt() : void 0;
11554
11411
  const skillSource = this.resolveSkillSource(options.skills, storage);
11555
11412
  const skillList = skillSource !== void 0 ? await skillSource.list() : void 0;
11556
11413
  const apiConfig = this.resolveApiConfig(options.api);
11557
11414
  const offloadConfig = this.resolveOffloadConfig(options.compaction?.toolResultOffload);
11558
11415
  const knowledgeRuntime = this.resolveKnowledgeRuntime(options.knowledge, storage);
11559
- let systemPrompt = await buildSystemPrompt({
11560
- ...coordinatorBase !== void 0 ? { base: coordinatorBase } : {},
11561
- ...options.systemPromptBase !== void 0 ? { staticBase: options.systemPromptBase } : {},
11562
- ...options.systemPromptAppend !== void 0 && options.systemPromptAppend.length > 0 ? { platformAppend: options.systemPromptAppend } : {},
11563
- memory,
11564
- storage,
11565
- // When an override was supplied, skip the legacy disk-scan path.
11566
- skillsAutoload: options.skills !== void 0 ? false : this.config.skills.autoload,
11567
- ...this.config.skills.path !== void 0 ? { skillsDir: this.config.skills.path } : {},
11568
- ...skillList !== void 0 ? { skillList } : {},
11569
- modelId: this.config.model.modelId,
11570
- provider: this.config.model.provider,
11571
- registeredToolNames: toolNameSet,
11572
- mcpTools,
11573
- coordinatorMode: isCoordinatorMode(this.config),
11574
- // Plan 047 — render API services catalog (lazy by default).
11575
- ...apiConfig !== void 0 && apiConfig.services.length > 0 ? {
11576
- apiServices: apiConfig.services,
11577
- apiCatalogMode: apiConfig.mode ?? "lazy",
11578
- ...apiConfig.lazyTokenThreshold !== void 0 ? { apiLazyTokenThreshold: apiConfig.lazyTokenThreshold } : {}
11579
- } : {}
11580
- });
11581
- if (options.outputFormat === "json") {
11582
- systemPrompt += "\n\n" + buildSchemaPrompt(options.outputSchema);
11583
- }
11584
11416
  const gate = this.resolveGate();
11585
11417
  const inspect = this.buildInspectWriter(storage.workspace, logPath);
11586
- const registry = buildToolRegistry({
11587
- config: this.config,
11418
+ const { systemPrompt: assembledPrompt, registry } = await this.buildPromptAndRegistry({
11419
+ runOptions: options,
11420
+ coordinatorBase,
11588
11421
  storage,
11589
11422
  client,
11590
- parentLogPath: logPath,
11591
- parentAgentId: null,
11423
+ logPath,
11592
11424
  subagentRegistry,
11593
- system: systemPrompt,
11594
11425
  agents,
11595
11426
  mcpTools,
11596
11427
  memory,
11597
11428
  inspect,
11598
- ...this.config.hooks.propagateGateToSubagents === true && gate !== void 0 ? { subagentGate: gate } : {},
11599
- ...skillSource !== void 0 ? { skillSource } : {},
11600
- ...apiConfig !== void 0 ? { apiConfig } : {},
11601
- ...offloadConfig !== void 0 ? { toolResultOffload: offloadConfig } : {},
11602
- ...knowledgeRuntime !== void 0 ? { knowledge: knowledgeRuntime } : {},
11603
- ...this.internals.fetch !== void 0 ? { fetch: this.internals.fetch } : {}
11429
+ gate,
11430
+ skillSource,
11431
+ skillList,
11432
+ apiConfig,
11433
+ offloadConfig,
11434
+ knowledgeRuntime
11604
11435
  });
11436
+ let systemPrompt = assembledPrompt;
11437
+ if (options.outputFormat === "json") {
11438
+ systemPrompt += "\n\n" + buildSchemaPrompt(options.outputSchema);
11439
+ }
11605
11440
  await inspect.writeStartSnapshot({
11606
11441
  systemPrompt,
11607
11442
  tools: this.snapshotTools(registry, mcpTools),
11443
+ // Plan 052 review fix — surface the resume-time effective
11444
+ // policy in `run-options.json` so post-hoc debuggers see
11445
+ // exactly what gating drove the resume's prompt + tool
11446
+ // surface. Otherwise resume's inspect bundle looked like a
11447
+ // fresh run with no restrictions, masking the silent-widening
11448
+ // failure mode documented on `ResumeOptions.tools`.
11608
11449
  runOptions: scrubRunOptions({
11609
11450
  runId: snapshot.runId,
11610
11451
  nodeId: snapshot.nodeId,
11611
11452
  task: "[resumed run \u2014 original task in transcript]",
11612
11453
  ...options.outputFormat !== void 0 ? { outputFormat: options.outputFormat } : {},
11613
- ...options.outputSchema !== void 0 ? { outputSchema: options.outputSchema } : {}
11454
+ ...options.outputSchema !== void 0 ? { outputSchema: options.outputSchema } : {},
11455
+ ...options.tools !== void 0 ? { tools: options.tools } : {},
11456
+ ...options.toolChoice !== void 0 ? { toolChoice: options.toolChoice } : {},
11457
+ ...options.systemPromptBase !== void 0 ? { systemPromptBase: options.systemPromptBase } : {},
11458
+ ...options.systemPromptAppend !== void 0 ? { systemPromptAppend: options.systemPromptAppend } : {}
11614
11459
  }),
11615
11460
  modelConfig: scrubModelConfig(
11616
11461
  {
@@ -12259,72 +12104,97 @@ ${inputJson}
12259
12104
  });
12260
12105
  }
12261
12106
  /**
12262
- * Collect the names of all tools that will be registered — used to
12263
- * populate the system prompt's tool-specific instructions BEFORE
12264
- * building the registry itself (the prompt goes into the registry's
12265
- * Agent tool as the child system prompt, so the prompt must exist
12266
- * first).
12107
+ * Plan 052 two-pass system-prompt + tool-registry build.
12108
+ *
12109
+ * The prompt's API / skill / MCP sections describe only the tools
12110
+ * the model will actually see, so we must know the post-filter
12111
+ * tool set before assembling the prompt. The registry, however,
12112
+ * is what produces that set, AND its `Agent` tool needs the
12113
+ * final system prompt baked in for subagent dispatch.
12114
+ *
12115
+ * The chicken-and-egg is resolved by building twice:
12116
+ *
12117
+ * Pass 1 — placeholder prompt → registry → applyRunToolFilter
12118
+ * → snapshot of visible tool names + visible MCP tools.
12119
+ * Pass 2 — final prompt assembled from those visible surfaces
12120
+ * → rebuild registry → applyRunToolFilter again.
12121
+ *
12122
+ * `applyRunToolFilter` is deterministic given options, so the
12123
+ * second filter pass is identical to the first; the second
12124
+ * registry is the authoritative one returned to the caller.
12125
+ *
12126
+ * Pass 1's prompt is intentionally minimal (placeholder string)
12127
+ * because the only thing we use the first registry for is its
12128
+ * post-filter tool list. The transient prompt never reaches the
12129
+ * model.
12267
12130
  */
12268
- collectToolNames(mcpTools) {
12269
- const names = /* @__PURE__ */ new Set();
12270
- const builtins = [
12271
- // Bash is no longer registered by the engine (Plan 020) — but
12272
- // we keep it in the prompt-generation list so any caller that
12273
- // adds it via `tools.custom` (or its capabilityStub equivalent)
12274
- // gets a consistent prompt mention.
12275
- "Bash",
12276
- "Read",
12277
- "Write",
12278
- "Edit",
12279
- "Glob",
12280
- "Grep",
12281
- "WebFetch",
12282
- "WebSearch",
12283
- "Agent",
12284
- "SkillPage",
12285
- "Sleep",
12286
- "NotebookEdit",
12287
- "TaskCreate",
12288
- "TaskGet",
12289
- "TaskList",
12290
- "TaskUpdate",
12291
- "Memorize",
12292
- "Recall",
12293
- "ToolSearch"
12294
- ];
12295
- const enabled = new Set(this.config.tools.enabled);
12296
- const disabled = new Set(this.config.tools.disabled);
12297
- const wantAll = enabled.has("*");
12298
- for (const name of builtins) {
12299
- if (disabled.has(name)) continue;
12300
- if (wantAll || enabled.has(name)) names.add(name);
12301
- }
12302
- if ((this.config.api?.services.length ?? 0) > 0) {
12303
- if (!disabled.has("ApiCall") && (wantAll || enabled.has("ApiCall"))) {
12304
- names.add("ApiCall");
12305
- }
12306
- }
12307
- if (this.config.compaction.toolResultOffload?.enabled === true) {
12308
- if (!disabled.has("FetchData") && (wantAll || enabled.has("FetchData"))) {
12309
- names.add("FetchData");
12310
- }
12311
- }
12312
- if (this.config.knowledge?.enabled === true) {
12313
- if (!disabled.has("SearchKnowledge") && (wantAll || enabled.has("SearchKnowledge"))) {
12314
- names.add("SearchKnowledge");
12315
- }
12316
- if (!disabled.has("ReadKnowledge") && (wantAll || enabled.has("ReadKnowledge"))) {
12317
- names.add("ReadKnowledge");
12318
- }
12319
- }
12320
- for (const tool of this.config.tools.custom) {
12321
- names.add(tool.name);
12322
- }
12323
- for (const tool of mcpTools) {
12324
- if (disabled.has(tool.name)) continue;
12325
- if (wantAll || enabled.has(tool.name)) names.add(tool.name);
12326
- }
12327
- return names;
12131
+ async buildPromptAndRegistry(args) {
12132
+ const {
12133
+ runOptions,
12134
+ coordinatorBase,
12135
+ storage,
12136
+ client,
12137
+ logPath,
12138
+ subagentRegistry,
12139
+ agents,
12140
+ mcpTools,
12141
+ memory,
12142
+ inspect,
12143
+ gate,
12144
+ skillSource,
12145
+ skillList,
12146
+ apiConfig,
12147
+ offloadConfig,
12148
+ knowledgeRuntime
12149
+ } = args;
12150
+ const baseRegistryArgs = {
12151
+ config: this.config,
12152
+ storage,
12153
+ client,
12154
+ parentLogPath: logPath,
12155
+ parentAgentId: null,
12156
+ subagentRegistry,
12157
+ agents,
12158
+ mcpTools,
12159
+ memory,
12160
+ inspect,
12161
+ ...this.config.hooks.propagateGateToSubagents === true && gate !== void 0 ? { subagentGate: gate } : {},
12162
+ ...skillSource !== void 0 ? { skillSource } : {},
12163
+ ...apiConfig !== void 0 ? { apiConfig } : {},
12164
+ ...offloadConfig !== void 0 ? { toolResultOffload: offloadConfig } : {},
12165
+ ...knowledgeRuntime !== void 0 ? { knowledge: knowledgeRuntime } : {},
12166
+ ...this.internals.fetch !== void 0 ? { fetch: this.internals.fetch } : {}
12167
+ };
12168
+ const tempRegistry = buildToolRegistry({ ...baseRegistryArgs, system: "" });
12169
+ applyRunToolFilter(tempRegistry, runOptions);
12170
+ const visibleToolNames = new Set(tempRegistry.list().map((t) => t.name));
12171
+ const visibleMcpTools = mcpTools.filter((t) => visibleToolNames.has(t.name));
12172
+ const systemPrompt = await buildSystemPrompt({
12173
+ ...coordinatorBase !== void 0 ? { base: coordinatorBase } : {},
12174
+ ...runOptions.systemPromptBase !== void 0 ? { staticBase: runOptions.systemPromptBase } : {},
12175
+ ...runOptions.systemPromptAppend !== void 0 && runOptions.systemPromptAppend.length > 0 ? { platformAppend: runOptions.systemPromptAppend } : {},
12176
+ memory,
12177
+ storage,
12178
+ // When an override was supplied, skip the legacy disk-scan path.
12179
+ skillsAutoload: skillSource !== void 0 ? false : this.config.skills.autoload,
12180
+ ...this.config.skills.path !== void 0 ? { skillsDir: this.config.skills.path } : {},
12181
+ ...skillList !== void 0 ? { skillList } : {},
12182
+ // Plan 052 — pass the FINAL post-filter visible tool surface
12183
+ // so prompt sections gate correctly.
12184
+ visibleToolNames,
12185
+ mcpTools: visibleMcpTools,
12186
+ coordinatorMode: isCoordinatorMode(this.config),
12187
+ // Plan 047 — render API services catalog (lazy by default).
12188
+ // Gating on `ApiCall` visibility lives inside buildSystemPrompt.
12189
+ ...apiConfig !== void 0 && apiConfig.services.length > 0 ? {
12190
+ apiServices: apiConfig.services,
12191
+ apiCatalogMode: apiConfig.mode ?? "lazy",
12192
+ ...apiConfig.lazyTokenThreshold !== void 0 ? { apiLazyTokenThreshold: apiConfig.lazyTokenThreshold } : {}
12193
+ } : {}
12194
+ });
12195
+ const registry = buildToolRegistry({ ...baseRegistryArgs, system: systemPrompt });
12196
+ applyRunToolFilter(registry, runOptions);
12197
+ return { systemPrompt, registry };
12328
12198
  }
12329
12199
  /**
12330
12200
  * Resolve the subagent catalogue the Agent tool will dispatch against.
@@ -12962,6 +12832,60 @@ function buildToolRegistry(options) {
12962
12832
  }
12963
12833
  return registry;
12964
12834
  }
12835
+ function validateInitialMessages(raw) {
12836
+ if (!Array.isArray(raw)) {
12837
+ return { ok: false, message: "initialMessages must be an array" };
12838
+ }
12839
+ const { maxMessages, maxTotalChars, maxCharsPerMessage } = INITIAL_MESSAGES_LIMITS;
12840
+ if (raw.length > maxMessages) {
12841
+ return {
12842
+ ok: false,
12843
+ message: `initialMessages exceeds max of ${String(maxMessages)} messages (got ${String(raw.length)})`
12844
+ };
12845
+ }
12846
+ const out = [];
12847
+ let totalChars = 0;
12848
+ for (let i = 0; i < raw.length; i++) {
12849
+ const m = raw[i];
12850
+ if (m === null || typeof m !== "object") {
12851
+ return { ok: false, message: `initialMessages[${String(i)}] must be an object` };
12852
+ }
12853
+ if (m.role !== "user" && m.role !== "assistant") {
12854
+ return {
12855
+ ok: false,
12856
+ message: `initialMessages[${String(i)}].role must be 'user' or 'assistant'`
12857
+ };
12858
+ }
12859
+ if (typeof m.content !== "string") {
12860
+ return {
12861
+ ok: false,
12862
+ message: `initialMessages[${String(i)}].content must be a string`
12863
+ };
12864
+ }
12865
+ const trimmed = m.content.trim();
12866
+ if (trimmed.length === 0) {
12867
+ return {
12868
+ ok: false,
12869
+ message: `initialMessages[${String(i)}].content must not be empty`
12870
+ };
12871
+ }
12872
+ if (trimmed.length > maxCharsPerMessage) {
12873
+ return {
12874
+ ok: false,
12875
+ message: `initialMessages[${String(i)}].content exceeds ${String(maxCharsPerMessage)} chars`
12876
+ };
12877
+ }
12878
+ totalChars += trimmed.length;
12879
+ if (totalChars > maxTotalChars) {
12880
+ return {
12881
+ ok: false,
12882
+ message: `initialMessages total content exceeds ${String(maxTotalChars)} chars`
12883
+ };
12884
+ }
12885
+ out.push({ role: m.role, content: trimmed });
12886
+ }
12887
+ return { ok: true, messages: out };
12888
+ }
12965
12889
 
12966
12890
  // src/index.ts
12967
12891
  init_contract();