@sema-agent/server 7.82.1 → 7.84.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 (84) hide show
  1. package/dist/bench/s1/arms.d.ts +4 -4
  2. package/dist/bench/s1/arms.js +1 -2
  3. package/dist/bench/s1/live-deps.js +0 -1
  4. package/dist/bench/s1/runner-ctx.d.ts +3 -3
  5. package/dist/bench/s1/tasks.d.ts +8 -4
  6. package/dist/boot/resolve-spec.js +9 -5
  7. package/dist/capabilities/memory-notice.d.ts +6 -4
  8. package/dist/capabilities/memory-notice.js +1 -1
  9. package/dist/config-catalog.js +0 -1
  10. package/dist/config.js +1 -0
  11. package/dist/http/admission.js +7 -3
  12. package/dist/http/wire-types.d.ts +22 -0
  13. package/dist/leader/endpoint.js +2 -0
  14. package/dist/leader/leader.d.ts +33 -6
  15. package/dist/leader/leader.js +54 -45
  16. package/dist/leader/merge.d.ts +38 -29
  17. package/dist/leader/merge.js +45 -38
  18. package/dist/leader/wire.d.ts +2 -17
  19. package/dist/leader/wire.js +5 -58
  20. package/dist/memory-scope.d.ts +33 -5
  21. package/dist/memory-scope.js +10 -4
  22. package/dist/observability/fail-open.d.ts +5 -5
  23. package/dist/observability/fail-open.js +5 -2
  24. package/dist/observability/run-terminal-log.d.ts +2 -7
  25. package/dist/observability/run-terminal-log.js +119 -21
  26. package/dist/plugins/adoption-log-sql.d.ts +3 -0
  27. package/dist/plugins/adoption-log-sql.js +5 -1
  28. package/dist/plugins/approval-ask-store-sql.d.ts +3 -0
  29. package/dist/plugins/approval-ask-store-sql.js +9 -5
  30. package/dist/plugins/background-agent-store-sql.d.ts +3 -0
  31. package/dist/plugins/background-agent-store-sql.js +7 -2
  32. package/dist/plugins/device-store-sql.d.ts +3 -0
  33. package/dist/plugins/device-store-sql.js +9 -4
  34. package/dist/plugins/ensure-index.d.ts +42 -0
  35. package/dist/plugins/ensure-index.js +81 -0
  36. package/dist/plugins/ensure-schema-lock-policy.d.ts +29 -0
  37. package/dist/plugins/ensure-schema-lock-policy.js +31 -0
  38. package/dist/plugins/image-bake-store-sql.d.ts +3 -0
  39. package/dist/plugins/image-bake-store-sql.js +6 -2
  40. package/dist/plugins/image-index-sql.d.ts +3 -0
  41. package/dist/plugins/image-index-sql.js +8 -4
  42. package/dist/plugins/leader-run-store-sql.d.ts +3 -0
  43. package/dist/plugins/leader-run-store-sql.js +6 -2
  44. package/dist/plugins/mailbox-store-sql.d.ts +3 -0
  45. package/dist/plugins/mailbox-store-sql.js +6 -1
  46. package/dist/plugins/memory-engine-pg.d.ts +4 -0
  47. package/dist/plugins/memory-engine-pg.js +6 -6
  48. package/dist/plugins/memory-engine-tidb.js +5 -0
  49. package/dist/plugins/memory-sync-store-pg.d.ts +5 -0
  50. package/dist/plugins/memory-sync-store-pg.js +9 -2
  51. package/dist/plugins/memory-sync-store-tidb.js +6 -0
  52. package/dist/plugins/one-time-migrations.d.ts +1 -1
  53. package/dist/plugins/one-time-migrations.js +7 -14
  54. package/dist/plugins/outcome-ledger-sql.d.ts +3 -0
  55. package/dist/plugins/outcome-ledger-sql.js +7 -3
  56. package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
  57. package/dist/plugins/permission-rule-store-sql.js +9 -5
  58. package/dist/plugins/pg-cost-quota.d.ts +3 -0
  59. package/dist/plugins/pg-cost-quota.js +5 -1
  60. package/dist/plugins/pg-pool.d.ts +23 -2
  61. package/dist/plugins/pg-pool.js +22 -37
  62. package/dist/plugins/pg-rate-limiter.d.ts +3 -0
  63. package/dist/plugins/pg-rate-limiter.js +5 -1
  64. package/dist/plugins/pg-session-storage.d.ts +3 -0
  65. package/dist/plugins/pg-session-storage.js +8 -4
  66. package/dist/plugins/retention-store-sql.d.ts +3 -0
  67. package/dist/plugins/retention-store-sql.js +7 -3
  68. package/dist/plugins/roster-store-sql.d.ts +3 -0
  69. package/dist/plugins/roster-store-sql.js +6 -1
  70. package/dist/plugins/schema-indexes.d.ts +21 -0
  71. package/dist/plugins/schema-indexes.js +25 -0
  72. package/dist/plugins/task-attachment-store.d.ts +3 -0
  73. package/dist/plugins/task-attachment-store.js +7 -2
  74. package/dist/plugins/tidb-pool.d.ts +15 -3
  75. package/dist/plugins/tidb-pool.js +38 -15
  76. package/dist/plugins/tool-result-store-sql.d.ts +3 -0
  77. package/dist/plugins/tool-result-store-sql.js +5 -1
  78. package/dist/request-key-closure.d.ts +7 -0
  79. package/dist/request-key-closure.js +1 -1
  80. package/dist/request-value-closure.d.ts +31 -0
  81. package/dist/request-value-closure.js +18 -0
  82. package/dist/trace/project.d.ts +2 -1
  83. package/dist/trace/redact.d.ts +6 -0
  84. package/package.json +1 -1
@@ -22,7 +22,7 @@
22
22
  * 3. WORKER-ENV: the repair leg ALWAYS passes `workerEnv` so runRepairLoop's §5.1 isolation identity check is LIVE.
23
23
  * 4. PAIRED-SEED: `seed` is best-effort N-repeat replicate, NEVER a GO criterion; rows are distributional.
24
24
  * 5. COMPLETE TEAM COST: TEAM stats = Σ(worker stats.costBreakdown) + (merge.repair?.costUsd) +
25
- * (merge.conflictResolverCostUsd) — all three legs (the resolver cost was just un-dropped, merge.ts).
25
+ * 两条腿(7.83.0 起:冲突解决模型已随 [ref] ④ 删除,不再有第三条 resolver 腿)。
26
26
  *
27
27
  * 🔴 BLOCKED-but-honest structure: the REAL firm run is clay-gated (expensive E2B+DeepSeek). This file builds the
28
28
  * REAL arm logic against INJECTABLE profile seams (`ProfileDeps`) so the deterministic smoke drives it with a MOCK
@@ -143,9 +143,9 @@ export declare function classifyRunStatus(input: {
143
143
  */
144
144
  export declare function captureStats(stats: TaskResult["stats"] | undefined): EmittedStats;
145
145
  /**
146
- * 🔴 Corr #5 — the COMPLETE TEAM `stats`. Σ(worker stats.costBreakdown + humanReview) PLUS the merge legs:
147
- * `repair?.costUsd` and `conflictResolverCostUsd` (the resolver cost was just un-dropped in merge.ts). All three
148
- * legs, or TEAM C1 is undercounted ([ref]'s #1 confound). costUsd (float USD) → µUSD into llmRoot.
146
+ * 🔴 Corr #5 — the COMPLETE TEAM `stats`. Σ(worker stats.costBreakdown + humanReview) PLUS the merge leg
147
+ * `repair?.costUsd`. Both legs, or TEAM C1 is undercounted ([ref]'s #1 confound). costUsd (float USD) →
148
+ * µUSD into llmRoot. (7.83.0:第三条腿 `conflictResolverCostUsd` 随冲突解决模型删除,见下。)
149
149
  */
150
150
  export declare function captureTeamStats(reports: WorkerReport[], merge: MergeResult | undefined): EmittedStats;
151
151
  /**
@@ -60,8 +60,7 @@ export function captureTeamStats(reports, merge) {
60
60
  acc.humanReview.gates.push(...s.humanReview.gates);
61
61
  }
62
62
  const repairUsd = merge?.repair?.costUsd ?? 0;
63
- const resolverUsd = merge?.conflictResolverCostUsd ?? 0;
64
- acc.costBreakdown.llmRootMicroUsd += Math.round((repairUsd + resolverUsd) * 1e6);
63
+ acc.costBreakdown.llmRootMicroUsd += Math.round(repairUsd * 1e6);
65
64
  return acc;
66
65
  }
67
66
  export async function driveSupSuspendResume(initial, deps, implSpec, trap, advanceClock) {
@@ -362,7 +362,6 @@ async function runLeaderForTrap(rt, brain, models, roles, pricing, trap, cellId,
362
362
  const targetRef = "refs/heads/main";
363
363
  process.env.LEADER_ENABLED = "true";
364
364
  process.env.LEADER_WORKER_BUDGET_USD = String((rt.maxCostUsd ?? 1.0) / 2);
365
- process.env.LEADER_CONFLICT_ROUNDS = process.env.LEADER_CONFLICT_ROUNDS ?? "2";
366
365
  const run = createLeaderRunner({
367
366
  brain, models, roles, pricing, e2bApiKey: rt.e2bApiKey, workspace: W,
368
367
  ...(s1DebugGit() ? { logger: { warn: (m, x) => console.error(`[S1_DEBUG_LEADER warn] ${m}`, x ? JSON.stringify(x).slice(0, 300) : ""), info: (m, x) => console.error(`[S1_DEBUG_LEADER info] ${m}`, x ? JSON.stringify(x).slice(0, 200) : "") } } : {}),
@@ -8,9 +8,9 @@
8
8
  * `maxTokens`/`maxCostUsd` is a silently-unread dead key), and TEAM's per-worker budgets sum to ≤ the solo budget — so no
9
9
  * arm can win on a bigger pie. `assertBudgetMatch` (below) is the build-time guard the shape test runs.
10
10
  *
11
- * 🔴 BUDGET-MATCH for TEAM is NEW WIRING the live conflict test does NOT do: `leader-conflict-resolver-live.test.ts`
12
- * sets NO budget on `SubtaskSpec.spec` and NO `now` on any Runner. The harness STAMPS both (arms.ts) — copying the
13
- * live test verbatim would yield an unbounded TEAM (the confound) and an empty `humanReview` (C2 = undefined).
11
+ * 🔴 BUDGET-MATCH for TEAM is NEW WIRING no leader live test does: the (now-deleted, 7.83.0 / [ref] ④) conflict
12
+ * live test set NO budget on `SubtaskSpec.spec` and NO `now` on any Runner. The harness STAMPS both (arms.ts) —
13
+ * copying a live test verbatim would yield an unbounded TEAM (the confound) and an empty `humanReview` (C2 = undefined).
14
14
  *
15
15
  * The real-infra construction (E2B worker/grader envs + a live brain) is deferred to the live make-real proof
16
16
  * (`test/s1-value-live.test.ts`, gated on E2B+DEEPSEEK). This module only fixes the SHAPE + the budget invariant
@@ -3,7 +3,9 @@
3
3
  *
4
4
  * 🔴 FIRM vs DIRECTIONAL (a HIGH review correction — do NOT reproduce the false "5 LIVE-VERIFIED" claim):
5
5
  * Only C1 / C2 / C4 have a real E2B+DeepSeek live make-real test in `test/` today
6
- * (supervisor-verify-fix-live / supervisor-honest-bound-live / leader-conflict-resolver-live).
6
+ * (supervisor-verify-fix-live / supervisor-honest-bound-live;C4 的那条 `leader-conflict-resolver-live`
7
+ * 已随 7.83.0 / clay [ref] ④ **删除** —— 它证的是「冲突解决模型合两边」,而那条通道不存在了;C4 今天
8
+ * 没有 live 证明位,按 DIRECTIONAL 计,不得再算作 FIRM).
7
9
  * C3 (honest-PARTIAL) has NO dedicated live test; C5 (safety-escalate) is ONLY a unit test
8
10
  * (`supervisor-safety-escalate.test.ts` header: "no E2B / no DeepSeek / no DB" — it only exercises
9
11
  * `createDurableAskPolicy` in-memory, NEVER a real boundary). Counting either as a measured datapoint is the
@@ -84,9 +86,11 @@ export declare const C1_VERIFY_FIX: TrapSpec;
84
86
  */
85
87
  export declare const C2_HONEST_BOUND: TrapSpec;
86
88
  /**
87
- * C4 — fan-out CONFLICT (two workers overlap one file → merge conflict → the bounded resolver keeps both).
88
- * FIRM (leader-conflict-resolver-live.test.ts). TEAM-primary. Objective COMMAND oracle = both functions exist
89
- * AND compute correctly.
89
+ * C4 — fan-out CONFLICT (two workers overlap one file → merge conflict).
90
+ * 🔴 7.83.0 / clay [ref] ④:合不上不再起解决模型,leader 把 `conflict` 段(分支 + 冲突文件)交给用户、
91
+ * run 状态 `needs_human` ⇒ 这一格的 TEAM 臂在冲突形上**不会自己合好**。原 FIRM 证明位
92
+ * (`leader-conflict-resolver-live` live 测试)随通道删除,C4 退回 DIRECTIONAL。TEAM-primary。
93
+ * Objective COMMAND oracle = both functions exist AND compute correctly.
90
94
  */
91
95
  export declare const C4_PARALLEL: TrapSpec;
92
96
  /**
@@ -25,6 +25,7 @@ import { gateExecutionLane } from "../runtime-caps-resolver.js";
25
25
  import { applyRuntimeGovernance, stripDelegationTools } from "../runtime-governance.js";
26
26
  import { HttpError, encodeCheckpointScope } from "../security.js";
27
27
  import { memorySpecForRequest } from "../memory-scope.js";
28
+ import { singleMemberOffFieldIssue } from "../request-value-closure.js";
28
29
  import { a2aForScenario, mcpForScenario } from "../config-center/facade.js";
29
30
  import { normalizeAttachments, normalizeResilience, normalizeResumeAtMode, normalizeSuggestNextPrompts, promptProfileFromBody, resolveTaskLimits, retainBackgroundProcessesFromBody, taskAgentsSpecFragment, toolMaterializeStrategyFromBody, toolNameListFromBody } from "../spec-fields.js";
30
31
  import { cwdHonored, deviceCwdHonored, effectiveHostWorkspace, inProcessSingleUserLane, isValidCwd, parseAdditionalDirectories, satisfiedByProcessCwd, shellEnvMismatchCount } from "../task-cwd.js";
@@ -36,6 +37,7 @@ import { redactSecrets } from "../trace/redact.js";
36
37
  import { DeferredSandboxPathEnv, isSandboxPathAdjudicationLane, sandboxPathEnvSlots } from "./deferred-sandbox-path-env.js";
37
38
  import { executionLaneHomeDir } from "../execution-lane-caps.js";
38
39
  import { effectiveMemoryPersistenceCapable } from "./memory-boundary.js";
40
+ import { memoryFaceMounted } from "../memory-scope.js";
39
41
  export function createResolveSpec(ctx) {
40
42
  const { config, logger, metrics, localRoot, scenarios, principalCaps, centerRuntimeCapsResolver, getCenterPrompts, getKeyResolver, taskAttachmentStore, perSessionCwd, setSessionCwd, setSessionShellEnv, fenceSessionOwner, hookLlm, hookAgent, fleetBus, hookWakeBus, noteSessionShellGate, resumeAnchorStore, ownerAware, taskLimitCaps, taskTimeoutSec, selectEnvTool, sendUserFileToolSpec, memoryEngine, durableEnabled, approvalExemptionStore, singleUserAutoAcceptBaseline, checkpointStore, deploymentHooks, imageIndex, perTaskImage, sessionEnvSelection, liveQuestionFace, approverSeat, lockedKeys, } = ctx;
41
43
  assertGuardPatternsUsable(config);
@@ -59,9 +61,9 @@ export function createResolveSpec(ctx) {
59
61
  throw new HttpError(400, `cwd "${conflict.cwd.asSpelled}" is inside this session's scratchpad "${conflict.scratchpadDir.asSpelled}" — the scratchpad is the fs-write gate's EXEMPT directory, so a task rooted there would auto-approve every write under its own root and the engine refuses to build the gate at all (config.write_exempt_dir_covers_root). Point cwd at the project directory instead (or send no cwd and let the session keep the one it has): this session registers the cwd you send, so a task that happens not to mount the write gate would still leave every LATER task on this session rooted in the exemption. A workflow that really is meant to write without prompts says so with a knob that means it (a permission mode, an open write face) and still keeps its root outside the scratchpad — those knobs do not lift this refusal, because the conflict is between two fields of this request, not a permission level.`, { code: "request.field_conflict", extra: { cwd: conflict.cwd, scratchpadDir: conflict.scratchpadDir } });
60
62
  }
61
63
  }
62
- const rawMemoryCapture = body.memoryCapture;
63
- if (rawMemoryCapture !== undefined && rawMemoryCapture !== "off") {
64
- throw new HttpError(400, "memoryCapture must be the string 'off' when present (the session-level memory-capture opt-out declaration; absent = capture as usual — there is no 'on' spelling)", { code: "request.field_invalid" });
64
+ const offFieldIssue = singleMemberOffFieldIssue(body);
65
+ if (offFieldIssue) {
66
+ throw new HttpError(400, offFieldIssue.message, { code: "request.field_invalid" });
65
67
  }
66
68
  assertRequestA2aUnlocked(body.a2aPeers, lockedKeys);
67
69
  const gated = await gateScenarioAndAppend(body, auth, opts);
@@ -373,7 +375,9 @@ export function createResolveSpec(ctx) {
373
375
  return { ...(Object.keys(merged).length > 0 ? { limits: merged } : {}), ...(declarations.length > 0 ? { configOverrides: declarations } : {}) };
374
376
  })(),
375
377
  ...(config.requirePrincipal !== true ? { backgroundScope: "session" } : {}),
376
- memory: memoryEngine ? memorySpecForRequest(auth?.memoryScope, body.memoryWrite, s4DefaultScopes, { multiTenant: config.requirePrincipal === true }, body.memoryCapture) : undefined,
378
+ memory: memoryEngine
379
+ ? memorySpecForRequest(auth?.memoryScope, body.memoryWrite, s4DefaultScopes, { multiTenant: config.requirePrincipal === true }, { memory: body.memory, memoryCapture: body.memoryCapture })
380
+ : undefined,
377
381
  ...((v) => (v !== undefined ? { memoryPersistenceCapable: v } : {}))(effectiveMemoryPersistenceCapable(config)),
378
382
  tools: ((base) => {
379
383
  const extra = [...(selectEnvTool ? [selectEnvTool] : []), ...(sendUserFileToolSpec ? [sendUserFileToolSpec] : [])];
@@ -533,7 +537,7 @@ export function createResolveSpec(ctx) {
533
537
  governed = { ...governed, tools: stripDelegationTools(governed.tools) };
534
538
  }
535
539
  if (shouldDiscloseNoPersistentMemory({
536
- memory: governed.memory,
540
+ memoryFaceMounted: memoryFaceMounted(governed.memory),
537
541
  declaredCapable: effectiveMemoryPersistenceCapable(config),
538
542
  hands: gated.hands,
539
543
  handsReadOnly: governed.handsReadOnly === true,
@@ -18,7 +18,7 @@
18
18
  * roster 把落盘路**整批**卸掉时,手带在场也等于没有写通道),但负控仍然成立:必须**全部**已知落盘路
19
19
  * 都不在才算断链 —— `excludeTools:["Write"]` 不算(Bash/Edit 还在),请求面摘一个工具名改不动结论。
20
20
  */
21
- import { type PromptProvider, type TaskSpec } from "@sema-agent/core";
21
+ import { type PromptProvider } from "@sema-agent/core";
22
22
  import type { ScenarioHands } from "./hands-lane.js";
23
23
  /**
24
24
  * 「已知落盘路」的工具名闭集 —— 与 core 的 `rosterCanPersist` 同口径:**文件写工具**(Write/Edit/
@@ -35,9 +35,11 @@ export declare const KNOWN_PERSISTENCE_TOOL_NAMES: readonly ["Write", "Edit", "N
35
35
  export declare const NO_PERSISTENT_MEMORY_SECTION_ID = "sema/memory.no-persistent";
36
36
  /** 判别式的全部输入 —— 每一位都是**部署/装配期事实**,没有一位来自请求体的自我表态。 */
37
37
  export interface MemoryFaceFacts {
38
- /** 本次任务最终的 `TaskSpec.memory`。`undefined` = 这一腿根本没有引擎记忆面(引擎未接、多租户 dark、
39
- * remote lane 平面分裂 fail-closed……三条路殊途同归:core 不会 prepare 记忆,也就不会挂任何披露)。 */
40
- memory: TaskSpec["memory"];
38
+ /** 这一腿挂不挂记忆面 —— 由唯一属主 `memory-scope.ts` `memoryFaceMounted(spec.memory)` 算出(与 core `prepareMemory`
39
+ * 同判:`normalizeMemorySpec(...)?.enabled === true`)。`false` = 引擎未接 / 多租户 dark / remote lane fail-closed /
40
+ * S-405 `memory:"off"`(键在场但 `enabled:false`)—— 殊途同归:core 不会 prepare 记忆,也就不会挂任何披露。
41
+ * 🔴 不再收 `TaskSpec.memory` 本体:按「键在不在场」判是 7.84.0 前的病(合并复审 车HF F1)。 */
42
+ memoryFaceMounted: boolean;
41
43
  /** 部署声明 `MEMORY_PERSISTENCE_CAPABLE`(三态)。`true` = operator 自证有推断看不见的持久通道
42
44
  * (自定义 writer / MCP 记忆服务)⇒ 披露会变成假话,撤回;`false` = 强制披露;缺席 ⇒ 走推断。 */
43
45
  declaredCapable: boolean | undefined;
@@ -9,7 +9,7 @@ function anyPersistenceToolSurvives(excludeTools) {
9
9
  return KNOWN_PERSISTENCE_TOOL_NAMES.some((name) => !excluded.has(name));
10
10
  }
11
11
  export function shouldDiscloseNoPersistentMemory(facts) {
12
- if (facts.memory !== undefined)
12
+ if (facts.memoryFaceMounted)
13
13
  return false;
14
14
  if (facts.declaredCapable !== undefined)
15
15
  return !facts.declaredCapable;
@@ -184,7 +184,6 @@ export const CONFIG_CATALOG = [
184
184
  r("KUBERNETES_SERVICE_HOST", "orchestration", "string", "平台注入的 in-cluster k8s API host(K8S_API_URL 缺席时用)"),
185
185
  r("KUBERNETES_SERVICE_PORT", "orchestration", "string", "平台注入的 in-cluster k8s API 端口(缺省 443)"),
186
186
  r("LEADER_BUDGET_USD", "orchestration", "number", "leader replan-lite 扇出花费帽(USD)", { danger: BILL, derivedDefaultNote: "缺席 ⇒ replan-lite 自默认" }),
187
- r("LEADER_CONFLICT_ROUNDS", "orchestration", "number", "leader 合并冲突修复轮数", { danger: BILL, derivedDefaultNote: "缺席 ⇒ 场景配置 conflictRounds(默认 0)" }),
188
187
  r("LEADER_ENABLED", "orchestration", "boolean", "leader 端点(POST/GET /v1/leader;需隔离执行后端 + durable leader_run 店,缺则拒启)", { staticDefault: "false", configKey: "leaderEnabled" }),
189
188
  r("LEADER_FANOUT_ENABLED", "orchestration", "boolean", "leader 扇出许可(false=钉死单路由零 router 成本)", { staticDefault: "true", danger: BILL, configKey: "leaderFanoutEnabled" }),
190
189
  r("LEADER_MEASURE_GATES", "orchestration", "boolean", "leader 合并 push-hold-on-signal + measure-drives-repair 门(严格 'true' 才开)", { staticDefault: "false" }),
package/dist/config.js CHANGED
@@ -119,6 +119,7 @@ const RETIRED_ENVS = [
119
119
  ["OA_ISSUE_REPO", "the OA scenario was removed in 5.0.0", "removed"],
120
120
  ["OA_ISSUE_LABELS", "the OA scenario was removed in 5.0.0", "removed"],
121
121
  ["APPROVAL_POLL_MS", "the OA poll-approval lane was removed in 5.0.0; the durable checkpoint lane is the only gate", "removed"],
122
+ ["LEADER_CONFLICT_ROUNDS", "the bounded merge-conflict-resolution agent was deleted in 7.83.0 按 C-R63 删除,合不上交用户:leader 交回 result.conflict(分支 + 冲突文件),run 状态 needs_human", "removed"],
122
123
  ];
123
124
  function rejectRetiredEnvs() {
124
125
  const hit = RETIRED_ENVS.filter(([o]) => process.env[o] !== undefined && process.env[o] !== "");
@@ -3,6 +3,7 @@ import { isModelAllowlisted, matchCatalogModel } from "../model-select.js";
3
3
  import { HttpError } from "../security.js";
4
4
  import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_PERMISSION_RULES, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS, parseTaskSettings, skillListingBudgetFractionOk, skillListingMaxDescOk, taskSettingsKeyIssue } from "../task-settings.js";
5
5
  import { taskBodyKeyIssue } from "../request-key-closure.js";
6
+ import { singleMemberOffFieldIssue } from "../request-value-closure.js";
6
7
  import { selfOrchestrationDenial } from "../task-workflow.js";
7
8
  import { centerEntitlementSourceWired } from "../runtime-caps-resolver.js";
8
9
  import { parseHooksConfig } from "../hooks/hook-runner.js";
@@ -307,9 +308,12 @@ export function createAdmission(ctx) {
307
308
  sendError(res, 400, "request.field_invalid", "memoryWrite must be a boolean (false = pause memory writes for this run)");
308
309
  return null;
309
310
  }
310
- if (body.memoryCapture !== undefined && body.memoryCapture !== "off") {
311
- sendError(res, 400, "request.field_invalid", "memoryCapture must be the string 'off' when present (the session-level memory-capture opt-out declaration; absent = capture as usual — there is no 'on' spelling)");
312
- return null;
311
+ {
312
+ const offFieldIssue = singleMemberOffFieldIssue(body);
313
+ if (offFieldIssue) {
314
+ sendError(res, 400, "request.field_invalid", offFieldIssue.message);
315
+ return null;
316
+ }
313
317
  }
314
318
  if (body.requireExistingSession !== undefined && typeof body.requireExistingSession !== "boolean") {
315
319
  sendError(res, 400, "request.field_invalid", "requireExistingSession must be a boolean");
@@ -320,6 +320,28 @@ export interface TaskRequestBody {
320
320
  * 记忆面暗的部署(引擎未接线 / 多租户)上本键随 `memory` 整体缺席而无处落 —— 那里本就零采集。
321
321
  * Rides the persisted body onto resume legs(与 memoryWrite 同姿势)。 */
322
322
  memoryCapture?: "off";
323
+ /** core [ref] r5 §5 W3(server S-405):**请求级「记忆整面关」声明** —— `"off"` = 这一次运行**不挂记忆面**
324
+ * (没有 `# Memory` 段/索引/三只记忆工具/写准入)。单成员闭集,**没有** `"on"`(缺席就是记忆照常挂的唯一形;
325
+ * 声明只能单向收紧)。透传为 `TaskSpec.memory.enabled:false` —— ⚠️ **保留事实,不折成整键缺席**:core 的
326
+ * 终局观测面把「有 spec 但被关」读作 `reason:"disabled"`、把「没有 spec」读作 `reason:"no-spec"`,审计上
327
+ * 不是一件事,所以本仓铸的是 `{ …正常 scopes, enabled:false }`。
328
+ * ⚠️ **语义如实**:这是「不挂记忆面」,**不是**文件系统级的禁读禁写 —— 记忆根若落在本次授权的工作区内,
329
+ * 普通 Read/Write 照走普通围栏(没有记忆面 = 没有记忆写门,不是多了一道禁令)。
330
+ * 与 `memoryCapture:"off"` **不是一个轴**:那是「记忆照常挂,但本会话的内容永远不进长期记忆」
331
+ * (采集面,落一条单向会话记录);这是「本次运行整个记忆面都不挂」(读写整面,逐请求)。
332
+ * 🔴 **两键同发时不要以为买到了两件事**(亲核 core 7.21.1 `prepare-memory.js:48/:55`):core 把 capture 腿与
333
+ * 引擎挂载合取,`enabled:false` 下 capture 的**单向会话记录不落**(core 发 `phase:"config"` 一行披露)⇒
334
+ * 下一 turn 不带本键时采集照常。要「这个会话永久别记」,那一 turn 就**别**同时发本键。详见契约 §12.7。
335
+ * 与 `memoryWrite:false`(本 run 只读平面:照常挂、照常读、harvest 不提交)同样正交、同样可并存。
336
+ * 🔴 **与 `GET /v1/capabilities` 的 legacy 能力位 `memory`(恒假)同名不同物**:那一位说的是退役的
337
+ * MemoryStore HTTP 动词族,与本键零关系 —— 别按那一位判断本键可不可用。
338
+ * ⚠️ 拼写门**两腿同判、响亮拒**:fresh 400 `request.field_invalid`;resume 重放同判(不做 resume 降级 ——
339
+ * 把一条收紧声明按打字错误静默丢掉是不可逆方向的静默降级)。两腿共用同一只判据
340
+ * (`request-value-closure.ts` 的 `singleMemberOffFieldIssue`,与 `memoryCapture` 同表)。
341
+ * 记忆面暗的部署(引擎未接线 / 多租户)上 `memory` 整键本就缺席,本声明无处落也无事可关。
342
+ * Rides the persisted body onto resume legs(与 `memoryWrite` 同姿势)。
343
+ * ⏳ 子代地板(`ParentSpecSeat.memoryOff`:父关 ⇒ 子代/孙代同关)候 core 7.22.0,**本版不做**。 */
344
+ memory?: "off";
323
345
  /** 142-S4 projectId 线程化([ref] §2):这个 run 归属的项目(center 登记簿键,generic lowercase
324
346
  * UUID — core S1 `resolveProjectId` marker 或 S3 无仓 mint 的产物;客户端只透传,零铸造权威)。是「哪个
325
347
  * 项目」的选择器,不是身份/capability — scope 的 tenant 段永远来自 verified principal。SHAPE 门在
@@ -3,6 +3,8 @@ import { redactErrorMessage, redactHead, redactLeaderResult, redactLeaderResultW
3
3
  import { withPrincipal } from "../observability/principal-context.js";
4
4
  import { leaderIdemKey } from "../plugins/leader-run-store-sql.js";
5
5
  function statusForLeaderResult(result) {
6
+ if (result.conflict)
7
+ return "needs_human";
6
8
  switch (result.repairTerminal) {
7
9
  case "candidate_only":
8
10
  case "needs_human_oracle":
@@ -104,10 +104,13 @@ export interface LeaderDeps {
104
104
  /** Replan-lite ([ref] §6): bounded, DETERMINISTIC reactions — no runtime DAG surgery. When set:
105
105
  * worker failed → redispatch ×1 on a FRESH env (clean base; a crashed worker's half-written workspace is
106
106
  * never reused) → still failed = collapse-remainder-to-single; verify FAIL (repair already bounded inside
107
- * the verify loop) → collapse; structural merge conflict → collapse + record; budgetUsd exceeded → stop
108
- * fan-out, single continues. Multi-trigger arbitration priority (C6): suspended > budget > merge-conflict >
109
- * verify-fail > worker-failed — only the highest-priority reaction runs. (The suspended reaction itself —
110
- * cancel the others, exempt the suspended worker, surface its token — is unconditional, not gated here.) */
107
+ * the verify loop) → collapse; budgetUsd exceeded → stop fan-out, single continues. Multi-trigger
108
+ * arbitration priority (C6): suspended > budget > verify-fail > worker-failed — only the highest-priority
109
+ * reaction runs. (The suspended reaction itself — cancel the others, exempt the suspended worker, surface
110
+ * its token — is unconditional, not gated here.)
111
+ *
112
+ * 🔴 S-113 / clay [ref] ④(7.83.0):**merge-conflict 不再是一条 replan 臂**。worker 分支合不上 ⇒ 不起
113
+ * collapse 模型,`conflict` 段(分支 + 冲突文件)交给**用户**,见 {@link LeaderResult.conflict}。 */
111
114
  replan?: {
112
115
  /** Fan-out budget (USD, sum of worker costs). Exceeded → stop fan-out, collapse to single (C5). */
113
116
  budgetUsd?: number;
@@ -154,12 +157,36 @@ export interface LeaderResult {
154
157
  route?: "single" | "fanout";
155
158
  /** Set when planning/provisioning failed before fan-out. */
156
159
  error?: string;
157
- /** Replan-lite ([ref] §6): which trigger won arbitration + what was done. */
160
+ /** Replan-lite ([ref] §6): which trigger won arbitration + what was done.
161
+ * 🔴 S-113 / [ref] ④(7.83.0):闭集删掉 `"merge-conflict"`(硬 breaking,零别名——消费方的 tsc 红即通知)。 */
158
162
  replan?: {
159
- trigger: "suspended" | "budget" | "merge-conflict" | "verify-fail" | "worker-failed";
163
+ trigger: "suspended" | "budget" | "verify-fail" | "worker-failed";
160
164
  redispatched?: string[];
161
165
  collapsed?: string;
162
166
  };
167
+ /**
168
+ * 🔴 S-113 / clay [ref] ④(7.83.0)——**合不上 = 交给用户**(对齐 CC 形)。merge 的 `phase:"apply"` 冲突
169
+ * 不再起 collapse 模型:`ok=false` + 本段,用户拿 `workers[].branch` + `files` 自己 `git merge` / cherry-pick
170
+ * (worker 分支本就留在集成沙箱 / 远端)。run 状态走既有第三终局 `needs_human`(endpoint 的状态映射),不加
171
+ * 新状态词。**缺席何义** = 这一趟没有冲突,或对面是老 server。
172
+ */
173
+ conflict?: {
174
+ /** 用户侧的合并基点 = leader 这一趟的 durable base。 */
175
+ baseSha: string;
176
+ /** 有冲突标记 / `.rej` 的路径(**不含内容**)。 */
177
+ files: string[];
178
+ /** 清单被截断(探测到的冲突路径多于上限)时在场;**缺席 = 这就是全部**(对抗复审 F5)。 */
179
+ filesTruncated?: true;
180
+ /** 全部 completed worker 的分支;`applied:false` = 这只 worker 的补丁没进树(冲突者本人,或排在它后面
181
+ * 还没轮到 apply 的——顺序 apply,首次失败即停)。 */
182
+ workers: Array<{
183
+ workerId: string;
184
+ branch: string;
185
+ applied: boolean;
186
+ }>;
187
+ /** `.rej` 头(经既有脱敏面,≤ 2 KiB)——给人看冲突长什么样。 */
188
+ rejHead?: string;
189
+ };
163
190
  /** C4: suspended workers' resume tokens, surfaced to the caller (their checkpoints + paused envs are intact —
164
191
  * the single-agent resume path owns them from here; orphan protection = the existing deadline reap). */
165
192
  suspended?: Array<{
@@ -1,8 +1,8 @@
1
1
  import { fanOut } from "./fanout.js";
2
- import { redactErrorMessage, redactHead, redactPatchHead } from "../observability/run-terminal-log.js";
2
+ import { redactErrorMessage, redactHead } from "../observability/run-terminal-log.js";
3
3
  import { pullDiff } from "./diffout.js";
4
4
  import { mergeBranches } from "./merge.js";
5
- import { Runner, runWithVerification, resumeWithVerification, delimitUntrusted, terminalProjection } from "@sema-agent/core";
5
+ import { Runner, runWithVerification, resumeWithVerification, terminalProjection } from "@sema-agent/core";
6
6
  import { pausedCauseOf } from "../terminal.js";
7
7
  const MAX_RESUME_SLICES = 200;
8
8
  export async function autoResumeOnResource(initial, signal, resume, maxSlices = MAX_RESUME_SLICES) {
@@ -152,20 +152,25 @@ export async function runLeaderTask(task, durableBaseSha, deps) {
152
152
  : w.diffEnv
153
153
  ? pullDiff(w.diffEnv, w.baseSha, { ref: w.branch, ...(workerRepoDir ? { cwd: workerRepoDir } : {}) })
154
154
  : Promise.resolve({ ok: false, error: `worker ${w.workerId} has neither fetchDiff nor diffEnv` });
155
- const salvageNonCompleted = async (rs) => {
155
+ const notCompleted = (r) => r.status !== "completed";
156
+ const salvagePatches = async (rs) => {
156
157
  const out = [];
158
+ const skipped = (workerId, why, detail) => deps.logger?.warn?.("salvage_skipped", { workerId, why, ...(detail ? { detail: redactHead(detail, 200) } : {}) });
157
159
  await Promise.all(rs
158
- .filter((r) => r.status !== "completed")
159
160
  .map(async (r) => {
160
161
  const w = byId.get(r.workerId);
161
162
  if (!w)
162
- return;
163
+ return skipped(r.workerId, "unknown_worker");
163
164
  try {
164
165
  const d = await workerDiff(w);
165
- if (d.ok && d.patch.trim().length > 0)
166
- out.push({ workerId: r.workerId, sessionId: r.sessionId, patch: d.patch });
166
+ if (!d.ok)
167
+ return skipped(r.workerId, "diff_failed", d.error);
168
+ if (d.patch.trim().length === 0)
169
+ return skipped(r.workerId, "empty_diff");
170
+ out.push({ workerId: r.workerId, sessionId: r.sessionId, patch: d.patch });
167
171
  }
168
- catch {
172
+ catch (e) {
173
+ skipped(r.workerId, "diff_threw", e instanceof Error ? e.message : String(e));
169
174
  }
170
175
  }));
171
176
  return out;
@@ -226,7 +231,7 @@ export async function runLeaderTask(task, durableBaseSha, deps) {
226
231
  if (sus.length === 0)
227
232
  return undefined;
228
233
  deps.logger?.info?.("replan_suspended", { workers: sus.map((r) => r.workerId) });
229
- const salvaged = await salvageNonCompleted(rs);
234
+ const salvaged = await salvagePatches(rs.filter(notCompleted));
230
235
  return {
231
236
  ok: false, reports: rs, cancelled: true, route,
232
237
  error: "worker suspended — fan-out cancelled (suspended worker exempt), resume token surfaced",
@@ -235,41 +240,26 @@ export async function runLeaderTask(task, durableBaseSha, deps) {
235
240
  ...(salvaged.length > 0 ? { salvaged } : {}),
236
241
  };
237
242
  };
238
- const collapseToSingle = async (trigger, reason, priorMerge) => {
243
+ const conflictSegment = (c, rs) => ({
244
+ baseSha: durableBaseSha,
245
+ files: c.files,
246
+ ...(c.filesTruncated ? { filesTruncated: true } : {}),
247
+ workers: rs
248
+ .filter((r) => r.status === "completed")
249
+ .map((r) => ({ workerId: r.workerId, branch: byId.get(r.workerId).branch, applied: c.applied.includes(r.workerId) })),
250
+ ...(c.rejHead ? { rejHead: c.rejHead } : {}),
251
+ });
252
+ const collapseToSingle = async (trigger, reason) => {
239
253
  const safeReason = redactHead(reason, 300);
240
254
  deps.logger?.warn?.("replan_collapse_to_single", { trigger, reason: safeReason });
241
255
  const replan = { trigger, collapsed: safeReason, ...(redispatched ? { redispatched } : {}) };
242
- const priorDiffs = [];
243
- await Promise.all(reports.map(async (r) => {
244
- const w = byId.get(r.workerId);
245
- if (!w)
246
- return;
247
- try {
248
- const d = await workerDiff(w);
249
- if (d.ok && d.patch.trim().length > 0)
250
- priorDiffs.push({ workerId: r.workerId, status: r.status, patch: d.patch });
251
- }
252
- catch {
253
- }
254
- }));
255
- if (priorDiffs.length > 0)
256
- deps.logger?.info?.("collapse_reuse_priors", { workers: priorDiffs.map((d) => d.workerId) });
257
- const soloObjective = priorDiffs.length === 0
258
- ? task
259
- : [
260
- task,
261
- ``,
262
- `--- PRIOR PARALLEL WORK — REUSE IT, do not re-derive from scratch ---`,
263
- `${priorDiffs.length} worker(s) attempted this task in parallel; their changes could NOT be integrated cleanly (${safeReason.slice(0, 150)}). Each worker's diff over the base is below. COPY the correct, non-overlapping parts verbatim and produce ONE coherent change for the WHOLE task, resolving only the overlaps. Treat these as a strong starting point — fix anything wrong, but do not throw the work away.`,
264
- ...priorDiffs.map((d) => delimitUntrusted(`PRIOR WORKER '${d.workerId}' (${d.status}) DIFF (untrusted)`, redactPatchHead(d.patch, 8000))),
265
- ].join("\n");
266
- const soloSub = { workerId: "collapse-solo", branch: "worker/collapse-solo", files: ["."], spec: { objective: soloObjective } };
256
+ const soloSub = { workerId: "collapse-solo", branch: "worker/collapse-solo", files: ["."], spec: { objective: task } };
267
257
  let solo;
268
258
  try {
269
259
  solo = await deps.provisionWorker(soloSub);
270
260
  }
271
261
  catch (e) {
272
- const salvaged = await salvageNonCompleted(finalReports);
262
+ const salvaged = await salvagePatches(finalReports.filter(notCompleted));
273
263
  return { ok: false, reports: finalReports, cancelled, route, error: `collapse provision failed: ${e instanceof Error ? e.message : String(e)}`, replan, ...(salvaged.length > 0 ? { salvaged } : {}) };
274
264
  }
275
265
  workers.push(solo);
@@ -280,10 +270,20 @@ export async function runLeaderTask(task, durableBaseSha, deps) {
280
270
  const sus = await surfaceSuspended(finalReports, fo.reports);
281
271
  if (sus)
282
272
  return { ...sus, replan: { ...replan, trigger: "suspended" }, ...(repairTerminal ? { repairTerminal } : {}), ...(candidatePatch ? { candidatePatch } : {}) };
283
- const mRaw = await mergeBranches(fo.reports, durableBaseSha, mergeDeps);
284
- const priorCost = priorMerge?.conflictResolverCostUsd ?? 0;
285
- const m = priorCost > 0 ? { ...mRaw, conflictResolverCostUsd: (mRaw.conflictResolverCostUsd ?? 0) + priorCost } : mRaw;
286
- const salvaged = await salvageNonCompleted(finalReports);
273
+ const m = await mergeBranches(fo.reports, durableBaseSha, mergeDeps);
274
+ if (!m.ok && m.conflict) {
275
+ const cc = m.conflict;
276
+ deps.logger?.warn?.("merge_conflict_to_user", { conflicted: cc.conflicted, applied: cc.applied, files: cc.files.slice(0, 20) });
277
+ const salvagedAll = await salvagePatches(finalReports);
278
+ return {
279
+ ok: false, reports: finalReports, merge: m, cancelled: fo.cancelled, route, replan,
280
+ conflict: conflictSegment(cc, fo.reports),
281
+ ...(salvagedAll.length > 0 ? { salvaged: salvagedAll } : {}),
282
+ ...(repairTerminal ? { repairTerminal } : {}),
283
+ ...(candidatePatch ? { candidatePatch } : {}),
284
+ };
285
+ }
286
+ const salvaged = await salvagePatches(finalReports.filter(notCompleted));
287
287
  return {
288
288
  ok: m.ok, reports: finalReports, merge: m, cancelled: fo.cancelled, route, replan,
289
289
  ...(salvaged.length > 0 ? { salvaged } : {}),
@@ -336,18 +336,27 @@ export async function runLeaderTask(task, durableBaseSha, deps) {
336
336
  const sus2 = await surfaceSuspended(reports, fo2.reports);
337
337
  if (sus2)
338
338
  return sus2;
339
- const stillFailed = fo2.reports.filter((r) => r.status !== "completed").map((r) => r.workerId);
339
+ const stillFailed = fo2.reports.filter(notCompleted).map((r) => r.workerId);
340
340
  if (stillFailed.length > 0) {
341
341
  return collapseToSingle("worker-failed", `redispatch failed again: ${stillFailed.join(", ")}`);
342
342
  }
343
343
  }
344
344
  }
345
345
  const merge = await mergeBranches(reports, durableBaseSha, mergeDeps);
346
- if (!merge.ok && merge.phase === "apply" && deps.replan) {
347
- deps.logger?.warn?.("replan_merge_conflict", { reason: merge.reason, quarantine: merge.quarantine ?? [] });
348
- return collapseToSingle("merge-conflict", merge.reason, merge);
346
+ if (!merge.ok && merge.conflict) {
347
+ const c = merge.conflict;
348
+ deps.logger?.warn?.("merge_conflict_to_user", { conflicted: c.conflicted, applied: c.applied, files: c.files.slice(0, 20) });
349
+ const salvagedC = await salvagePatches(reports);
350
+ return {
351
+ ok: false, reports, merge, cancelled, route,
352
+ conflict: conflictSegment(c, reports),
353
+ ...(redispatched ? { replan: { trigger: "worker-failed", redispatched } } : {}),
354
+ ...(salvagedC.length > 0 ? { salvaged: salvagedC } : {}),
355
+ ...(repairTerminal ? { repairTerminal } : {}),
356
+ ...(candidatePatch ? { candidatePatch } : {}),
357
+ };
349
358
  }
350
- const salvaged = await salvageNonCompleted(reports);
359
+ const salvaged = await salvagePatches(reports.filter(notCompleted));
351
360
  return {
352
361
  ok: merge.ok, reports, merge, cancelled, route,
353
362
  ...(redispatched ? { replan: { trigger: "worker-failed", redispatched } } : {}),
@@ -4,7 +4,8 @@
4
4
  * The deterministic Coordinator integrates N completed workers' branches under FULL isolation:
5
5
  * 1. pull each worker's patch OUT of its container (M2 `pullDiff` — no creds go in).
6
6
  * 2. provision an EPHEMERAL no-net/no-creds integration sandbox (base pre-provisioned by the dep).
7
- * 3. `git apply --3way` each patch in sequence (partial-apply → capture `.rej`, quarantine — council#3).
7
+ * 3. `git apply --3way` each patch in sequence (conflict ⇒ TERMINAL: capture `.rej` + the conflict PATH list and
8
+ * hand them to the USER as {@link MergeConflict} — S-113 / clay [ref] ④; council#3's quarantine unchanged).
8
9
  * 4. run the full test suite IN the sandbox (the gate; untrusted diff never touches the control plane — [26]§16).
9
10
  * 5. green → the **Coordinator** (trusted control plane, holds creds — NOT the sandbox, council#1) takes the
10
11
  * integrated patch and pushes with CAS `--force-with-lease=<ref>:<baseSha>` (push race → retry — council#2).
@@ -57,31 +58,25 @@ export interface IntegrationRepair {
57
58
  }>;
58
59
  }
59
60
  /**
60
- * Bounded merge-conflict resolution (search 2026-06-16). Symmetric with {@link IntegrationRepair}: when a worker's
61
- * patch CONFLICTS on `git apply --3way` (overlapping edits a sibling already touched), run a bounded agent IN the
62
- * integration sandbox to integrate THIS worker's patch — instead of the leader discarding ALL fan-out work and
63
- * redoing the whole task as one fresh worker (collapseToSingle, leader.ts: "correctness over thrift; half-done
64
- * adoption is future"). The agent merges the in-tree conflict markers + applies the `.rej` hunks; mergeBranches
65
- * then VERIFIES no marker/.rej remains before continuing, and the downstream test gate still guards correctness.
66
- * Wired by the caller (provisionIntegrationSandbox); when unset, an apply conflict stays terminal exactly as before
67
- * (zero behaviour change). Reuses the IntegrationRepair sandbox + hands pattern → no new core/env seam (the seam
68
- * [ref] §8 feared for "adopting N half-done workspaces" is unnecessary for the conflict case).
61
+ * S-113 / clay [ref] ④ — **合不上 = 交给用户**(对齐 CC 形)。`git apply --3way` 冲突时的产出:冲突**路径**
62
+ * (不含内容)+ 哪几只 worker 的补丁已经进树,由 leader 铸成 `LeaderResult.conflict` 段交到用户手上。
63
+ * 本段刻意**只有路径与身份**:CC 259 语料对 collapse / conflict resolution 零命中,CC 的并行是同机 teammates
64
+ * 各 worktree、结束由 git 重放到用户 HEAD、冲突列给用户(cli259.js @2303113)——没有任何模型去读别的 worker
65
+ * 的 diff。7.83.0 删掉的那条「冲突解决模型」通道(它的类型面与 `LEADER_CONFLICT_ROUNDS` 旋钮)正是此意。
69
66
  */
70
- export interface ConflictResolver {
71
- /** Max resolution attempts before giving up (each = one agent run + one marker/.rej re-check). */
72
- maxRounds: number;
73
- /** Run the resolver agent against the live conflicted tree. Edits the working tree to integrate the worker's
74
- * patch + removes the `.rej`/`.orig` files; MUST NOT commit / push / add (the merge squashes at the end). */
75
- run(ctx: {
76
- round: number;
77
- workerId: string;
78
- conflictText: string;
79
- patch: string;
80
- repoDir: string;
81
- }): Promise<{
82
- costUsd?: number;
83
- note?: string;
84
- }>;
67
+ export interface MergeConflict {
68
+ /** 有冲突标记或 `.rej` 的路径(**不含内容**;`.rej` 后缀已剥,指向源文件)。 */
69
+ files: string[];
70
+ /** 对抗复审 F5 [medium]:清单**被截断**(命中 {@link CONFLICT_FILES_MAX})时在场 —— 缺席 = 这就是全部。
71
+ * 「完整清单」是本终局对用户的补偿,一次静默的 200 封顶会让用户以为冲突只有 200 处。 */
72
+ filesTruncated?: true;
73
+ /** 冲突发生**之前**已经干净 apply 进树的 workerId(顺序 apply,失败即停)。 */
74
+ applied: string[];
75
+ /** 补丁没能 apply 的那只 worker。 */
76
+ conflicted: string;
77
+ /** `.rej` 头(经既有**工件**脱敏面 `redactArtifactText` —— 与 `rej` 同一批 diff 字节同一口 —— 再切 ≤ 2 KiB)
78
+ * ——给用户看「冲突长什么样」,不是给模型的输入。 */
79
+ rejHead?: string;
85
80
  }
86
81
  export interface MergeDeps {
87
82
  /** Pull one worker's patch from ITS container (M2 pullDiff bound to the worker's env). */
@@ -93,7 +88,6 @@ export interface MergeDeps {
93
88
  env: IntegrationEnv;
94
89
  destroy: () => Promise<void>;
95
90
  repair?: IntegrationRepair;
96
- conflictResolver?: ConflictResolver;
97
91
  }>;
98
92
  /**
99
93
  * The CREDENTIALED push, done by the Coordinator (control plane), NOT the sandbox (council#1). Takes the
@@ -177,8 +171,6 @@ export type MergeResult = {
177
171
  reason?: string;
178
172
  };
179
173
  repair?: RepairMeta;
180
- conflictsResolved?: string[];
181
- conflictResolverCostUsd?: number;
182
174
  terminal?: RepairTerminal;
183
175
  candidatePatch?: string;
184
176
  } | {
@@ -187,10 +179,10 @@ export type MergeResult = {
187
179
  phase: "no-workers" | "diff-out" | "provision" | "apply" | "test" | "push";
188
180
  quarantine?: string[];
189
181
  rej?: string;
182
+ conflict?: MergeConflict;
190
183
  retry?: boolean;
191
184
  details?: string;
192
185
  repair?: RepairMeta;
193
- conflictResolverCostUsd?: number;
194
186
  terminal?: RepairTerminal;
195
187
  candidatePatch?: string;
196
188
  measure?: {
@@ -200,4 +192,21 @@ export type MergeResult = {
200
192
  };
201
193
  /** Integrate completed workers' branches. Never throws — failures are typed MergeResult. */
202
194
  export declare function mergeBranches(reports: WorkerReport[], baseSha: string, deps: MergeDeps): Promise<MergeResult>;
195
+ /** 探测脚本里某一半失败时打到 stdout 的标记前缀(`grep` / `find` 各一);只由 {@link conflictFilesOf} 认。
196
+ * ⚠️ 判序承重:`conflictFilesOf` **先**测本前缀、**后**剥 `./` —— `grep -rl` / `find .` 输出的真路径恒带 `./`,
197
+ * 所以一个真叫 `__PROBE_FAIL_grep` 的文件到达时是 `./__PROBE_FAIL_grep`,不与标记二义;把剥 `./` 挪到前面就静默破功。 */
198
+ export declare const PROBE_FAIL_PREFIX = "__PROBE_FAIL_";
199
+ /**
200
+ * S-113 / [ref] ④ — 把 `grep -rIl` + `find` 的输出变成**冲突路径清单**(给用户,不含内容)。
201
+ * 逐行:去掉 `./` 前缀、剥掉 `.rej` 后缀(`pkg/a.ts.rej` 与 `pkg/a.ts` 是同一处冲突的两种痕迹)、去重、
202
+ * 按 {@link CONFLICT_FILES_MAX} 封顶并**交回是否截断**(F5:静默封顶会让用户把 200 当成全部)。两条命令合成
203
+ * 一次 exec,所以 stdout 里两族路径混在一起 —— 本函数是它唯一的解释者。任一半探测失败 ⇒ 那一半打出
204
+ * {@link PROBE_FAIL_PREFIX} 标记、另一半的清单照常回来 ⇒ `probeFailed` 点名失败的那半、`files` 只是**不完整**而不是
205
+ * 「没有冲突」:`conflict` 段仍在场(用户仍拿得到分支与冲突者身份),调用点另记一行响亮日志(合并复审 车HB H-7)。
206
+ */
207
+ export declare function conflictFilesOf(stdout: string): {
208
+ files: string[];
209
+ truncated: boolean;
210
+ probeFailed: string[];
211
+ };
203
212
  //# sourceMappingURL=merge.d.ts.map