@nowcrew/daemon 0.6.68 → 0.6.70

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 (72) hide show
  1. package/dist/agent-ability/materializer.js +30 -1
  2. package/dist/agent-ability/resolver.js +27 -16
  3. package/dist/agent-ability/runtime.js +3 -0
  4. package/dist/agent-ability/types.js +6 -11
  5. package/dist/automatic-install-target.js +15 -10
  6. package/dist/background-job.js +50 -0
  7. package/dist/computer-cli.js +10 -1
  8. package/dist/computer-service.js +41 -1
  9. package/dist/config.js +44 -0
  10. package/dist/console-formatter.js +76 -1
  11. package/dist/console.js +23 -4
  12. package/dist/daemon-migration-controller.js +16 -18
  13. package/dist/execution-protocol.js +0 -1
  14. package/dist/execution-runner.js +60 -11
  15. package/dist/i18n.js +2 -0
  16. package/dist/local-execution-prompt.js +32 -13
  17. package/dist/local-executor.js +107 -55
  18. package/dist/local-memory/index-budget.js +74 -0
  19. package/dist/local-memory/pending.js +111 -0
  20. package/dist/local-memory/runtime.js +87 -0
  21. package/dist/local-memory/types.js +1 -0
  22. package/dist/local-memory/write.js +448 -0
  23. package/dist/machine-info.js +3 -4
  24. package/dist/main.js +83 -0
  25. package/dist/managed-service-lifecycle.js +47 -2
  26. package/dist/managed-service-startup.js +2 -0
  27. package/dist/profile-layout.js +10 -4
  28. package/dist/project-dependencies.js +139 -124
  29. package/dist/project-dependency-lock.js +87 -0
  30. package/dist/project-dependency-manifest.js +2 -12
  31. package/dist/project-registry-wiring.js +1 -4
  32. package/dist/project-skills/controller.js +2 -0
  33. package/dist/project-skills/runtime-launch.js +10 -7
  34. package/dist/prompt.js +19 -1
  35. package/dist/provider-env.js +3 -21
  36. package/dist/repository-identity.js +45 -0
  37. package/dist/runner.js +4 -2
  38. package/dist/runtimes/codex-app-server-runner.js +55 -23
  39. package/dist/runtimes/codex-command-safety.js +75 -0
  40. package/dist/runtimes/codex-contract.js +20 -5
  41. package/dist/runtimes/codex-deepseek-config.js +7 -17
  42. package/dist/runtimes/codex-turn-retry.js +69 -0
  43. package/dist/runtimes/codex.js +4 -3
  44. package/dist/serve.js +68 -3
  45. package/dist/storage-config.js +175 -0
  46. package/dist/storage-control.js +193 -0
  47. package/dist/storage-copy.js +628 -0
  48. package/dist/storage-credential-guard.js +21 -0
  49. package/dist/storage-doctor.js +228 -0
  50. package/dist/storage-identity.js +29 -0
  51. package/dist/storage-layout.js +143 -0
  52. package/dist/storage-manifest.js +855 -0
  53. package/dist/storage-migration-controller.js +420 -0
  54. package/dist/storage-migration-manifest.js +105 -0
  55. package/dist/storage-migration-policy.js +100 -0
  56. package/dist/storage-runtime-controller.js +339 -0
  57. package/dist/storage-runtime-readiness.js +184 -0
  58. package/dist/storage-runtime.js +39 -0
  59. package/dist/storage-safe-fs.js +302 -0
  60. package/dist/storage-serve-adapter.js +65 -0
  61. package/dist/storage-status-delivery.js +209 -0
  62. package/dist/storage-status-replay.js +125 -0
  63. package/dist/storage-target-confirmation.js +129 -0
  64. package/dist/storage-worker-command.js +37 -0
  65. package/dist/storage-worker-executor.js +430 -0
  66. package/dist/storage-worker-handoff.js +151 -0
  67. package/dist/storage-worker-operation.js +284 -0
  68. package/dist/storage-worker-status.js +259 -0
  69. package/dist/supervised-runtime.js +3 -1
  70. package/dist/workspace.js +34 -14
  71. package/package.json +1 -1
  72. package/dist/self-training-workspace.js +0 -15
package/dist/console.js CHANGED
@@ -17,7 +17,7 @@ import { buildFilePreview, buildSnippetDiff } from "./console-payload.js";
17
17
  import { parseUnifiedDiff } from "./unified-diff.js";
18
18
  import { buildJsonResult } from "./json-result.js";
19
19
  import { buildCollapsedResult } from "./console-collapse.js";
20
- import { boundedCodexPlan, codexPlanText } from "./runtimes/codex-contract.js";
20
+ import { boundedCodexPlan } from "./runtimes/codex-contract.js";
21
21
  /** 单条工具返回正文上限(超出截断并标注),避免单条把终端/DB 撑爆。 */
22
22
  export const TOOL_RESULT_CAP = 4000;
23
23
  /** 工具输入摘要上限(标题行那一段)。 */
@@ -69,6 +69,12 @@ function summarizeToolInput(name, input) {
69
69
  .join(", ");
70
70
  return `⏺ ${name}(${clip(head.replace(/\s+/g, " "), TOOL_INPUT_CAP)})`;
71
71
  }
72
+ /** codex 计划步骤状态(pending/inProgress/completed) → ConsoleTodo 状态(pending/in_progress/completed)。 */
73
+ const CODEX_PLAN_STATUS_MAP = {
74
+ pending: "pending",
75
+ inProgress: "in_progress",
76
+ completed: "completed",
77
+ };
72
78
  const TODO_STATUSES = new Set(["pending", "in_progress", "completed"]);
73
79
  /** claude TodoWrite 的 todos 数组归一为 ConsoleTodo[](脏数据条目丢弃)。 */
74
80
  function normalizeTodos(raw) {
@@ -236,7 +242,13 @@ function codexItemChunks(item, td) {
236
242
  if (!todos.length)
237
243
  return [];
238
244
  const done = todos.filter((t) => t.status === "completed").length;
239
- return [{ stream: "tool", text: `⏺ Todos(${done}/${todos.length})`, payload: { kind: "todos", todos } }];
245
+ // codex 对同一份 plan 的多次更新复用同一个 item.id,透传给下游做合并键;无 id 时不伪造。
246
+ const listId = typeof item.id === "string" && item.id ? item.id : undefined;
247
+ return [{
248
+ stream: "tool",
249
+ text: `⏺ Todos(${done}/${todos.length})`,
250
+ payload: { kind: "todos", todos, ...(listId ? { listId } : {}) },
251
+ }];
240
252
  }
241
253
  if (item.type === "web_search" && item.query?.trim()) {
242
254
  return [{ stream: "tool", text: `⏺ WebSearch(${clip(item.query.trim(), TOOL_INPUT_CAP)})` }];
@@ -276,10 +288,17 @@ export function toConsoleLines(event) {
276
288
  }
277
289
  if (e.type === "turn.plan.updated") {
278
290
  const plan = boundedCodexPlan(e.plan);
279
- return plan === null ? [] : [{
291
+ if (plan === null)
292
+ return [];
293
+ const todos = plan.map(({ step, status }) => ({
294
+ text: step,
295
+ status: CODEX_PLAN_STATUS_MAP[status],
296
+ }));
297
+ const listId = typeof e.turn_id === "string" && e.turn_id ? e.turn_id : "codex-plan";
298
+ return [{
280
299
  stream: "tool",
281
300
  text: `⏺ ${td("Plan updated")} (${plan.length})`,
282
- payload: { kind: "plan", plan: codexPlanText(plan) },
301
+ payload: { kind: "todos", todos, listId },
283
302
  }];
284
303
  }
285
304
  if (e.type === "item.completed" && e.item) {
@@ -10,6 +10,7 @@ import { buildServiceSpec, captureExactService, serviceStatus, systemCommandRunn
10
10
  import { readManagedServiceRegistry } from "./managed-service-registry.js";
11
11
  import { uninstallManagedService } from "./managed-service-lifecycle.js";
12
12
  import { resolveAutomaticInstallTarget } from "./automatic-install-target.js";
13
+ import { buildStorageMigrationMappings, createSafeServiceManifest, CredentialMaterialError, } from "./storage-migration-manifest.js";
13
14
  const MigrationMessageSchema = z.object({
14
15
  type: z.literal("daemon:migrate"),
15
16
  migrationId: z.string().uuid(),
@@ -47,33 +48,30 @@ async function hashedFiles(root, directory = root) {
47
48
  return files;
48
49
  }
49
50
  async function backupAndCopy(input, id, targetHome, targetAgents, service) {
51
+ // Validate and project service identity before creating any backup bytes. A descriptor containing
52
+ // a token must stop the migration before it can be copied into the local backup root.
53
+ const safeService = createSafeServiceManifest({
54
+ spec: service.spec,
55
+ snapshot: service.snapshot,
56
+ managedRecord: service.managedRecord,
57
+ });
50
58
  const backup = migrationBackupRoot(input, id);
51
59
  await mkdir(backup, { recursive: true, mode: 0o700 });
52
60
  await cp(input.currentDaemonHome, resolve(backup, "daemon-home"), { recursive: true, force: false });
53
61
  await cp(input.currentAgentsRoot, resolve(backup, "agents-root"), { recursive: true, force: false });
54
62
  await cp(input.currentDaemonHome, targetHome, { recursive: true, force: false });
55
63
  await cp(input.currentAgentsRoot, targetAgents, { recursive: true, force: false });
56
- const files = [
57
- ...(await hashedFiles(resolve(backup, "daemon-home"))).map((file) => ({ ...file, path: `daemon-home/${file.path}` })),
58
- ...(await hashedFiles(resolve(backup, "agents-root"))).map((file) => ({ ...file, path: `agents-root/${file.path}` })),
59
- ];
64
+ const daemonFiles = await hashedFiles(resolve(backup, "daemon-home"));
65
+ const agentFiles = await hashedFiles(resolve(backup, "agents-root"));
66
+ const mappings = buildStorageMigrationMappings({ daemonFiles, agentFiles });
60
67
  await writeFile(resolve(backup, "manifest.json"), `${JSON.stringify({
61
- version: 1,
68
+ version: 2,
62
69
  migrationId: id,
63
70
  profile: input.profileName,
64
71
  daemonHome: input.currentDaemonHome,
65
72
  agentsRoot: input.currentAgentsRoot,
66
- service: {
67
- id: service.spec.id,
68
- platform: service.spec.platform,
69
- descriptorPath: service.spec.descriptorPath,
70
- descriptor: service.spec.descriptor,
71
- daemonCommand: service.spec.daemonCommand,
72
- installed: service.snapshot.installed,
73
- running: service.snapshot.running,
74
- managedRecord: service.managedRecord,
75
- },
76
- files,
73
+ service: safeService,
74
+ mappings,
77
75
  }, null, 2)}\n`, { mode: 0o600 });
78
76
  return backup;
79
77
  }
@@ -147,7 +145,7 @@ export function createDaemonMigrationController(input) {
147
145
  }
148
146
  }
149
147
  catch (error) {
150
- sendFailure(message.migrationId, "target_conflict", error);
148
+ sendFailure(message.migrationId, error instanceof CredentialMaterialError ? "credential_material" : "target_conflict", error);
151
149
  return;
152
150
  }
153
151
  input.sendStatus({ type: "daemon:migration-status", migrationId: message.migrationId, status: "backing_up" });
@@ -158,7 +156,7 @@ export function createDaemonMigrationController(input) {
158
156
  }
159
157
  catch (error) {
160
158
  await Promise.allSettled([rm(targetPath, { recursive: true, force: true }), rm(targetAgents, { recursive: true, force: true })]);
161
- sendFailure(message.migrationId, "backup_failed", error);
159
+ sendFailure(message.migrationId, error instanceof CredentialMaterialError ? "credential_material" : "backup_failed", error);
162
160
  return;
163
161
  }
164
162
  const install = input.install ?? installDaemonAutomatically;
@@ -197,7 +197,6 @@ export const ExecutionStartSchema = z.object({
197
197
  language: z.enum(["zh", "en"]),
198
198
  systemPrompt: z.string(),
199
199
  wakePrompt: z.string(),
200
- maintenancePurpose: z.literal("self_training").optional(),
201
200
  }).strict(),
202
201
  runtime: z.object({
203
202
  name: RuntimeSchema,
@@ -19,6 +19,7 @@ import { ProjectSkillRuntimeOwnershipUnverifiedError, } from "./project-skills/r
19
19
  import { projectSkillExecutionProjection, projectSkillProjectionErrorCode } from "./project-skills/execution-adapter.js";
20
20
  import { redactProjectSkillRuntimeRootError, } from "./project-skills/runtime-launch.js";
21
21
  import { createProjectRegistry } from "./project-skills/registry.js";
22
+ import { ProjectDependencyError, } from "./project-dependencies.js";
22
23
  import { ProjectContextUnavailableError, resolveProjectContext } from "./project-workspaces/resolver.js";
23
24
  import { PROJECT_WORKSPACES_CAPABILITY } from "./machine-info.js";
24
25
  import { PROJECT_SKILL_PROJECTION_V2_CAPABILITY } from "./project-skills/types.js";
@@ -315,7 +316,9 @@ function failedCompletion(spec, error, startedAt, finishedAt) {
315
316
  ? "queue_timeout"
316
317
  : error instanceof ProjectContextUnavailableError
317
318
  ? error.code
318
- : projectSkillProjectionErrorCode(error) ?? "local_execution_failed";
319
+ : error instanceof ProjectDependencyError
320
+ ? error.code
321
+ : projectSkillProjectionErrorCode(error) ?? "local_execution_failed";
319
322
  return ExecutionCompletedSchema.parse({
320
323
  type: "execution:completed",
321
324
  protocolVersion: 1,
@@ -503,21 +506,68 @@ export async function runExecution(config, input, dependencies) {
503
506
  await cancellable(dependencies.slot.ready, dependencies.cancellation);
504
507
  }
505
508
  let projectContext;
509
+ let trainingProjectContext;
506
510
  let sessionContextFingerprint;
507
511
  const logicalProjectContext = spec.workspace.projectContext;
508
- if (logicalProjectContext !== undefined && logicalProjectContext.projectIds.length > 0) {
512
+ dslog("project_dependencies.preflight", "项目依赖准备前置状态", {
513
+ execution_id: spec.executionId,
514
+ agent_handle: spec.agent.handle,
515
+ has_ability_release: spec.agent.abilityRelease !== undefined,
516
+ ability_release_id: spec.agent.abilityRelease?.releaseId,
517
+ ability_root_commit: spec.agent.abilityRelease?.rootCommit,
518
+ has_project_context: logicalProjectContext !== undefined,
519
+ project_ids: JSON.stringify(logicalProjectContext?.projectIds ?? []),
520
+ project_context_primary: logicalProjectContext?.primaryProjectId,
521
+ dependency_prepare_expected: spec.agent.abilityRelease !== undefined,
522
+ });
523
+ if (spec.agent.abilityRelease !== undefined && dependencies.projectDependencies !== undefined) {
524
+ if (dependencies.abilityRelease !== undefined) {
525
+ await cancellable(dependencies.abilityRelease.apply(config.agentsRoot, spec.agent.handle, spec.agent.abilityRelease), dependencies.cancellation);
526
+ }
527
+ const preparedProjectDependencies = await cancellable(dependencies.projectDependencies.prepare({
528
+ trainingRoot: join(config.agentsRoot, spec.agent.handle, "training"),
529
+ reposRoot: join(config.agentsRoot, "repos"),
530
+ lockRoot: join(config.agentsRoot, ".crew", "project-dependencies"),
531
+ }), dependencies.cancellation);
532
+ trainingProjectContext = Object.freeze({
533
+ projects: Object.freeze(preparedProjectDependencies.map((project) => Object.freeze({
534
+ repositoryKey: project.repositoryKey,
535
+ displayName: project.displayName,
536
+ repo: project.repo,
537
+ root: project.localRoot,
538
+ branch: project.branch,
539
+ resolvedCommit: project.resolvedCommit,
540
+ }))),
541
+ });
542
+ for (const project of preparedProjectDependencies) {
543
+ dslog("project_dependencies.prepared", "Training project dependency prepared", {
544
+ execution_id: spec.executionId,
545
+ agent_handle: spec.agent.handle,
546
+ repository_key: project.repositoryKey,
547
+ project_branch: project.branch,
548
+ project_branch_source: project.branchSource,
549
+ project_commit: project.resolvedCommit,
550
+ project_checkout_mode: project.checkoutMode,
551
+ });
552
+ }
553
+ }
554
+ else if (spec.agent.abilityRelease === undefined
555
+ && logicalProjectContext !== undefined
556
+ && logicalProjectContext.projectIds.length > 0) {
509
557
  const projectSnapshot = {
510
558
  projectIds: logicalProjectContext.projectIds,
511
559
  ...(logicalProjectContext.primaryProjectId === undefined
512
560
  ? {}
513
561
  : { primaryProjectId: logicalProjectContext.primaryProjectId }),
514
562
  };
515
- projectContext = await cancellable(resolveProjectContext(projectSnapshot, dependencies.projectWorkspaceRegistry ?? createProjectRegistry(config.agentsRoot)), dependencies.cancellation);
563
+ const projectWorkspaceRegistry = dependencies.projectWorkspaceRegistry
564
+ ?? dependencies.projectRegistry
565
+ ?? createProjectRegistry(config.agentsRoot);
566
+ projectContext = await cancellable(resolveProjectContext(projectSnapshot, projectWorkspaceRegistry), dependencies.cancellation);
516
567
  sessionContextFingerprint = projectSessionContextFingerprint(spec.runtime.name, projectSnapshot);
517
568
  }
518
- const selfTraining = spec.instructions.maintenancePurpose === "self_training";
519
569
  let recalledMemory = "";
520
- if (!selfTraining && spec.agent.memoryEnabled === true && dependencies.agentMemory !== undefined) {
570
+ if (spec.agent.memoryEnabled === true && dependencies.agentMemory !== undefined) {
521
571
  try {
522
572
  recalledMemory = await cancellable(dependencies.agentMemory.recall(spec.agent.handle, spec.instructions.wakePrompt, spec.executionId, spec.agent.memoryAgentId ?? null), dependencies.cancellation);
523
573
  }
@@ -776,9 +826,7 @@ export async function runExecution(config, input, dependencies) {
776
826
  const baseSystemPromptBudget = spec.agent.abilityRelease === undefined
777
827
  ? systemPromptBudget
778
828
  : Math.max(0, Math.min(systemPromptBudget, Math.max(32 * 1024, systemPromptBudget - 168 * 1024)));
779
- const systemPromptWithLocalFacts = withLocalExecutionFacts(spec.instructions.systemPrompt, baseSystemPromptBudget, {
780
- ...(selfTraining ? { isolateLocalMemory: true } : {}),
781
- });
829
+ const systemPromptWithLocalFacts = withLocalExecutionFacts(spec.instructions.systemPrompt, baseSystemPromptBudget, { collaborationTools: config.codexCollaborationTools && spec.runtime.name === "codex" });
782
830
  const boundedSystemPrompt = (context) => appendAgentMemoryContext(typeof systemPromptWithLocalFacts === "string"
783
831
  ? systemPromptWithLocalFacts
784
832
  : systemPromptWithLocalFacts(context), recalledMemory, baseSystemPromptBudget);
@@ -791,6 +839,7 @@ export async function runExecution(config, input, dependencies) {
791
839
  ...(spec.workspace.resumeKey === undefined ? {} : { resumeKey: spec.workspace.resumeKey }),
792
840
  ...(sessionContextFingerprint === undefined ? {} : { sessionContextFingerprint }),
793
841
  ...(projectContext === undefined ? {} : { projectContext }),
842
+ ...(trainingProjectContext === undefined ? {} : { trainingProjectContext }),
794
843
  ...(spec.context.wakeMessageId === undefined ? {} : { wakeMessageId: spec.context.wakeMessageId }),
795
844
  ...(spec.context.attachments === undefined ? {} : { attachments: spec.context.attachments }),
796
845
  ...(spec.agent.projectSkills === undefined
@@ -804,6 +853,7 @@ export async function runExecution(config, input, dependencies) {
804
853
  name: spec.runtime.name,
805
854
  ...(spec.runtime.model === undefined ? {} : { model: spec.runtime.model }),
806
855
  ...(spec.runtime.reasoning === undefined ? {} : { reasoning: spec.runtime.reasoning }),
856
+ codexCollaborationTools: config.codexCollaborationTools,
807
857
  },
808
858
  effectivePermission: permission,
809
859
  captureFinal: spec.reporting.captureFinal,
@@ -823,13 +873,13 @@ export async function runExecution(config, input, dependencies) {
823
873
  } : {}),
824
874
  },
825
875
  session: {
826
- enabled: selfTraining ? false : config.resume,
876
+ enabled: config.resume,
827
877
  warmMs: config.resumeWarmMs,
828
878
  budgetTokens: config.sessionBudgetTokens,
829
879
  softTokens: config.sessionSoftTokens,
830
880
  maxTurns: config.sessionMaxTurns,
831
881
  },
832
- ...(selfTraining ? { maintenancePurpose: "self_training" } : {}),
882
+ localMemoryV2Enabled: config.localMemoryV2Handles?.has(spec.agent.handle) === true,
833
883
  };
834
884
  const result = await Promise.race([
835
885
  execute(localInput, callbacks, localDependencies),
@@ -995,7 +1045,6 @@ export async function runExecution(config, input, dependencies) {
995
1045
  }
996
1046
  await dependencies.journal.complete(spec.executionId, completion);
997
1047
  if (completion.outcome === "succeeded"
998
- && spec.instructions.maintenancePurpose !== "self_training"
999
1048
  && spec.agent.memoryEnabled === true
1000
1049
  && memoryCaptureFinalText !== null
1001
1050
  && dependencies.agentMemory !== undefined) {
package/dist/i18n.js CHANGED
@@ -19,6 +19,8 @@ const zh = {
19
19
  "Claude session started": "Claude 会话启动",
20
20
  "Codex session started": "Codex 会话启动",
21
21
  "Plan updated": "计划更新",
22
+ "Thinking (~{{tokens}} tokens, content not streamed by Claude Code)": "思考(约 {{tokens}} tokens,Claude Code 未输出思考正文)",
23
+ "Thinking (content not streamed by Claude Code)": "思考(Claude Code 未输出思考正文)",
22
24
  "Files changed": "文件变更",
23
25
  "Run failed": "运行出错",
24
26
  "Run finished": "本轮结束",
@@ -4,7 +4,22 @@
4
4
  * server 提示词是权威部分,绝不裁剪;项目工作区提示词是绑定执行的强制部分,放不下就直接失败;
5
5
  * 只有本机事实(CREW_HOME / work-log / MEMORY 注入)是可选增强,按整段丢弃或按字节截断。
6
6
  */
7
- import { buildRuntimeWorkspacePrompt, capMemoryForInject, capWorkLogForInject, } from "./prompt.js";
7
+ import { buildCollaborationToolsPrompt, buildRuntimeWorkspacePrompt, capMemoryForInject, capWorkLogForInject, } from "./prompt.js";
8
+ const trainingProjectJson = (project) => JSON.stringify({
9
+ repositoryKey: project.repositoryKey,
10
+ displayName: project.displayName,
11
+ repo: project.repo,
12
+ branch: project.branch,
13
+ resolvedCommit: project.resolvedCommit,
14
+ root: project.root,
15
+ });
16
+ export function buildTrainingGitProjectPrompt(context) {
17
+ if (context === undefined || context.projects.length === 0)
18
+ return "";
19
+ return "\n\n## Training Git projects\n" + context.projects
20
+ .map((project) => "- " + trainingProjectJson(project) + "\n Run project commands with: cd <root> && <command>")
21
+ .join("\n");
22
+ }
8
23
  export function truncateUtf8(value, maxBytes) {
9
24
  if (maxBytes <= 0)
10
25
  return "";
@@ -31,28 +46,32 @@ function takeCompletePromptSections(sections, maxBytes) {
31
46
  }
32
47
  /** v1 server instructions opt in to daemon-owned paths and bounded local workspace context. */
33
48
  export function withLocalExecutionFacts(serverPrompt, maxBytes, options = {}) {
34
- return ({ workspace, resuming, projectContext }) => {
35
- const memory = options.isolateLocalMemory ? "" : capMemoryForInject(workspace.memory);
49
+ return ({ workspace, resuming, projectContext, trainingProjectContext }) => {
50
+ const memory = capMemoryForInject(workspace.memory);
36
51
  const workLog = resuming ? "" : capWorkLogForInject(workspace.workLog);
37
- const mandatoryWorkspacePrompt = buildRuntimeWorkspacePrompt(projectContext);
38
- const crewHome = options.isolateLocalMemory ? workspace.runDir : workspace.dir;
52
+ const collaborationPrompt = options.collaborationTools === true ? buildCollaborationToolsPrompt() : "";
53
+ const mandatoryWorkspacePrompt = `${buildRuntimeWorkspacePrompt(projectContext)}${buildTrainingGitProjectPrompt(trainingProjectContext)}`;
39
54
  const localFactSections = [`\n\n## Local execution facts
40
- - $CREW_HOME: ${crewHome}
55
+ - $CREW_HOME: ${workspace.dir}
41
56
  - $CREW_TASK_LOG: ${workspace.workLogPath}`,
42
57
  ...(memory ? [`\n\n## Injected MEMORY.md (bounded local context)\n${memory}`] : []),
43
58
  ...(workLog ? [`\n\n## Injected work-log (bounded local context)\n${workLog}`] : []),
44
59
  ];
45
- const localFacts = localFactSections.join("");
60
+ const serverPromptBytes = Buffer.byteLength(serverPrompt, "utf8");
61
+ const collaborationBytes = Buffer.byteLength(collaborationPrompt, "utf8");
62
+ const mandatoryBytes = Buffer.byteLength(mandatoryWorkspacePrompt, "utf8");
63
+ const requiredBytes = serverPromptBytes + collaborationBytes + mandatoryBytes;
64
+ // 必需预算与 projectContext 无关:协作段落永远参与;无协作时与旧实现逐字节等价。
65
+ if (collaborationPrompt !== "" && requiredBytes > maxBytes) {
66
+ throw new Error("collaboration_tools_prompt_budget_exceeded");
67
+ }
46
68
  if (mandatoryWorkspacePrompt !== "") {
47
- const serverPromptBytes = Buffer.byteLength(serverPrompt, "utf8");
48
- const mandatoryBytes = Buffer.byteLength(mandatoryWorkspacePrompt, "utf8");
49
- const requiredBytes = serverPromptBytes + mandatoryBytes;
50
69
  if (requiredBytes > maxBytes)
51
70
  throw new Error("project_workspace_prompt_budget_exceeded");
52
71
  const optionalFacts = takeCompletePromptSections(localFactSections, maxBytes - requiredBytes);
53
- return `${serverPrompt}${optionalFacts}${mandatoryWorkspacePrompt}`;
72
+ return `${serverPrompt}${collaborationPrompt}${optionalFacts}${mandatoryWorkspacePrompt}`;
54
73
  }
55
- const remaining = maxBytes - Buffer.byteLength(serverPrompt, "utf8");
56
- return `${serverPrompt}${truncateUtf8(localFacts, remaining)}`;
74
+ const remaining = maxBytes - requiredBytes;
75
+ return `${serverPrompt}${collaborationPrompt}${truncateUtf8(localFactSections.join(""), remaining)}`;
57
76
  };
58
77
  }