@lmzhen/dsh-evolution-review 0.4.0 → 0.5.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/lib/index.js CHANGED
@@ -2,7 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { createUserMessage } from "@deepseek-ai/dsh-llm";
4
4
  import { SessionId } from "@deepseek-ai/dsh-session";
5
- import { COMPLETION_SKILL_REVIEW_PROMPT, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MEMORY_REVIEW_MODEL, DEFAULT_REVIEW_CONTEXT_MESSAGES, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_MESSAGE_CHARS, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_REVIEW_TIMEOUT_MS, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_MODEL, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS, DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS, DEFAULT_SUBSTANTIVE_MIN_USER_CHARS, DEFAULT_USER_CHAR_LIMIT, MAX_TIMER_DELAY_MS, PROMPT_BUNDLE, advanceReview, assertSkillsRootAliasRetired, clampedNumber, clearReviewChannel, contentHash, evolutionIoAdapter, foldToolDispatches, foldTurn, markReviewChannel, newSkillLibrary, readDispatchSignal, redactSecrets, resolveOrigins, resolveRootConfig, reviewPrompt, sessionAudited, skillReadNameOf, sweepReviewChannelSessions, verifyPromptBundle } from "@lmzhen/dsh-evolution-core";
5
+ import { COMPLETION_SKILL_REVIEW_PROMPT, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MEMORY_REVIEW_MODEL, DEFAULT_REVIEW_CONTEXT_MESSAGES, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_MESSAGE_CHARS, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_REVIEW_TIMEOUT_MS, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_MODEL, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS, DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS, DEFAULT_SUBSTANTIVE_MIN_USER_CHARS, DEFAULT_USER_CHAR_LIMIT, MAX_TIMER_DELAY_MS, PROMPT_BUNDLE, advanceReview, assertSkillsRootAliasRetired, clampedNumber, clearReviewChannel, contentHash, evolutionIoAdapter, foldToolDispatches, foldTurn, markReviewChannel, newSkillLibrary, policyStageLimits, readDispatchSignal, redactSecrets, resolveOrigins, resolveRootConfig, reviewPrompt, sessionAudited, skillReadNameOf, sweepReviewChannelSessions, verifyPromptBundle } from "@lmzhen/dsh-evolution-core";
6
6
  import { validateEvolutionPlan } from "@lmzhen/dsh-evolution-plan-validator";
7
7
  //#region lib/types/session-state.js
8
8
  /**
@@ -149,9 +149,6 @@ function clampReviewConfig(rawConfig, ctx) {
149
149
  if (clamped.length > 0) ctx.logger.warn(`dsh-evolution-review: ${clamped.join(", ")} provided an invalid value; falling back to the default`);
150
150
  return config;
151
151
  }
152
- /** v30 REV-04/REV-02: read the policy snapshot off the (optional) policy
153
- * service through an `unknown` boundary — the Context augmentation types the
154
- * getter non-optionally, but at runtime the row can be absent. */
155
152
  function policySnapshotOf(source) {
156
153
  return source?.get?.();
157
154
  }
@@ -213,7 +210,7 @@ function apply(ctx, rawConfig = {}) {
213
210
  return;
214
211
  }
215
212
  if (event.type !== "turn/end") return;
216
- if (turnStarts.size >= COUNTER_SWEEP_THRESHOLD || cumulativeToolCalls.size >= COUNTER_SWEEP_THRESHOLD || completionInjected.size >= COUNTER_SWEEP_THRESHOLD || pendingCadenceReviews.size >= COUNTER_SWEEP_THRESHOLD || skipNextCadenceFire.size >= COUNTER_SWEEP_THRESHOLD || cadenceResetWarned.size >= COUNTER_SWEEP_THRESHOLD || lastTurnStart.size >= COUNTER_SWEEP_THRESHOLD) {
213
+ if (turnStarts.size >= COUNTER_SWEEP_THRESHOLD || pendingCadenceWarned.size >= COUNTER_SWEEP_THRESHOLD || cumulativeToolCalls.size >= COUNTER_SWEEP_THRESHOLD || completionInjected.size >= COUNTER_SWEEP_THRESHOLD || pendingCadenceReviews.size >= COUNTER_SWEEP_THRESHOLD || skipNextCadenceFire.size >= COUNTER_SWEEP_THRESHOLD || cadenceResetWarned.size >= COUNTER_SWEEP_THRESHOLD || lastTurnStart.size >= COUNTER_SWEEP_THRESHOLD) {
217
214
  const isAlive = (id) => ctx.agents.get(id) !== void 0;
218
215
  sweepDeadSessionEntries(turnStarts, isAlive);
219
216
  sweepDeadSessionEntries(lastTurnStart, isAlive);
@@ -904,6 +901,7 @@ function apply(ctx, rawConfig = {}) {
904
901
  io: evolutionIoAdapter(() => io.provider()),
905
902
  limits: {
906
903
  ...DEFAULT_SKILL_LIMITS,
904
+ ...policyStageLimits(policySnapshot),
907
905
  maxSkillContentChars: policySnapshot?.skillContentChars ?? DEFAULT_SKILL_LIMITS.maxSkillContentChars
908
906
  },
909
907
  ctx
@@ -1113,24 +1111,56 @@ function resultCallIdOf(event) {
1113
1111
  return null;
1114
1112
  }
1115
1113
  /**
1116
- * V10-10 (P2-11): render one `[result]` evidence line from a tool-result
1117
- * event payload. The former read (`data.output`) targeted a field that does
1118
- * not exist on the upstream rc.2 payload, so EVERY result line rendered an
1119
- * empty payload and the review subagent never saw tool output — the evidence
1120
- * chain silently starved while still spending its line budget. The payload
1121
- * text now comes from `data.message.content` tool-result blocks (inner text
1122
- * blocks joined, mirroring the user/assistant rendering above). A failure is
1123
- * marked by the payload-level `error` OR a block-level `isError`. The legacy
1124
- * pre-rc.2 shape (no `message`) is tolerated as an empty payload it never
1125
- * throws. Budget: 500 chars per line (the 12-line cap lives in
1126
- * buildReviewRequest and is unchanged).
1114
+ * V10-10 (P2-11) / A1 (audit P1-1): render one `[result]` evidence line from
1115
+ * a tool-result event payload. The former read (`data.output`) targeted a
1116
+ * field that does not exist on the rc.2 payload; the current contract covers
1117
+ * BOTH rc.2 result shapes:
1118
+ * - native `tool/result`: the outcome lives in `message.content`
1119
+ * tool-result blocks (inner text blocks joined); a failure is marked by
1120
+ * the payload-level `error` OR a block-level `isError`;
1121
+ * - PTC `tool/ptc-dispatch` settle: the outcome lives at the TOP level
1122
+ * `content` is the logged ContentBlock list and `isError` the flag; there
1123
+ * is no `message` wrapper. (Before A1 this shape rendered an empty line,
1124
+ * so every PTC session's evidence block starved while its plan prompt
1125
+ * still demanded evidence.)
1126
+ * The legacy pre-rc.2 shape (neither `message` nor a PTC settle marker) is
1127
+ * tolerated as an empty payload — it never throws. `identity`, when given, is
1128
+ * the dispatched tool's name + raw arguments, prepended so a settled dispatch
1129
+ * keeps the call identity its `[call]` line would have had. Budget: 500 chars
1130
+ * per line (the 12-line cap lives in buildReviewRequest and is unchanged).
1127
1131
  */
1128
- function renderToolResultLine(data) {
1132
+ function renderToolResultLine(data, identity) {
1129
1133
  const shape = data;
1130
- const content = shape?.message?.content;
1131
- const resultBlocks = (Array.isArray(content) ? content : []).filter((block) => block.type === "tool-result");
1132
- const output = resultBlocks.map((block) => Array.isArray(block.content) ? block.content.map((inner) => inner.type === "text" && typeof inner.text === "string" ? inner.text : "").join(" ") : typeof block.text === "string" ? block.text : "").join(" ").trim();
1133
- return `[result]${shape?.error || resultBlocks.some((block) => block.isError === true) ? " [ERROR]" : ""} ${output.slice(0, 500)}`;
1134
+ const nativeContent = shape?.message?.content;
1135
+ const resultBlocks = Array.isArray(nativeContent) ? nativeContent.filter((block) => block.type === "tool-result") : [];
1136
+ let output;
1137
+ let failed;
1138
+ if (shape?.message !== void 0) {
1139
+ output = resultBlocks.map((block) => Array.isArray(block.content) ? block.content.map((inner) => inner.type === "text" && typeof inner.text === "string" ? inner.text : "").join(" ") : typeof block.text === "string" ? block.text : "").join(" ").trim();
1140
+ failed = Boolean(shape.error) || resultBlocks.some((block) => block.isError === true);
1141
+ } else if (typeof shape?.subCallId === "string" && typeof shape.isError === "boolean") {
1142
+ output = textOfLoggedContent(shape.content);
1143
+ failed = shape.isError;
1144
+ } else {
1145
+ output = "";
1146
+ failed = false;
1147
+ }
1148
+ const head = identity === void 0 ? "" : `${identity.name} ${identity.argsRaw.slice(0, 200)} → `;
1149
+ return `[result]${failed ? " [ERROR]" : ""} ${head}${output.slice(0, 500)}`;
1150
+ }
1151
+ /** Text of a PTC settle `content` payload (the logged ContentBlock list):
1152
+ * text blocks and plain strings joined, anything else skipped. */
1153
+ function textOfLoggedContent(content) {
1154
+ if (typeof content === "string") return content.trim();
1155
+ if (!Array.isArray(content)) return "";
1156
+ return content.map((block) => {
1157
+ if (typeof block === "string") return block;
1158
+ if (block !== null && typeof block === "object") {
1159
+ const candidate = block;
1160
+ if (candidate.type === "text" && typeof candidate.text === "string") return candidate.text;
1161
+ }
1162
+ return "";
1163
+ }).join(" ").trim();
1134
1164
  }
1135
1165
  /**
1136
1166
  * PLAN S4.1 (2026-09-16, audit P2-12): text of one persisted content block,
@@ -1157,6 +1187,17 @@ function buildReviewRequest(session, kind, signal, maxMessages, maxMessageChars)
1157
1187
  }
1158
1188
  const toolLines = [];
1159
1189
  const events = session.snapshotEvents();
1190
+ const callIdentity = /* @__PURE__ */ new Map();
1191
+ const identityWindowStart = Math.max(0, events.length - 2e3);
1192
+ for (let index = events.length - 1; index >= identityWindowStart; index -= 1) {
1193
+ const event = events[index];
1194
+ const opened = readDispatchSignal(event);
1195
+ if (opened === null || callIdentity.has(opened.callId)) continue;
1196
+ callIdentity.set(opened.callId, {
1197
+ name: opened.name,
1198
+ argsRaw: typeof opened.arguments === "string" ? opened.arguments : JSON.stringify(opened.arguments ?? {})
1199
+ });
1200
+ }
1160
1201
  const openedCallIds = /* @__PURE__ */ new Set();
1161
1202
  for (let index = events.length - 1; index >= 0 && toolLines.length < 12; index -= 1) {
1162
1203
  const event = events[index];
@@ -1164,7 +1205,13 @@ function buildReviewRequest(session, kind, signal, maxMessages, maxMessageChars)
1164
1205
  if (answeredCallId !== null) {
1165
1206
  if (openedCallIds.has(answeredCallId)) continue;
1166
1207
  openedCallIds.add(answeredCallId);
1167
- toolLines.push(renderToolResultLine(event?.data));
1208
+ const rawSettle = event?.data;
1209
+ const settleData = rawSettle !== null && typeof rawSettle === "object" ? rawSettle : void 0;
1210
+ const identity = settleData !== void 0 && typeof settleData.name === "string" && settleData.name !== "" ? {
1211
+ name: settleData.name,
1212
+ argsRaw: typeof settleData.arguments === "string" ? settleData.arguments : JSON.stringify(settleData.arguments ?? {})
1213
+ } : callIdentity.get(answeredCallId);
1214
+ toolLines.push(renderToolResultLine(event?.data, identity));
1168
1215
  continue;
1169
1216
  }
1170
1217
  const opened = readDispatchSignal(event);
@@ -151,19 +151,28 @@ export declare function filterUnreadSkillOps(ops: Array<{
151
151
  name?: string;
152
152
  }>, readNames: ReadonlySet<string>): number;
153
153
  /**
154
- * V10-10 (P2-11): render one `[result]` evidence line from a tool-result
155
- * event payload. The former read (`data.output`) targeted a field that does
156
- * not exist on the upstream rc.2 payload, so EVERY result line rendered an
157
- * empty payload and the review subagent never saw tool output — the evidence
158
- * chain silently starved while still spending its line budget. The payload
159
- * text now comes from `data.message.content` tool-result blocks (inner text
160
- * blocks joined, mirroring the user/assistant rendering above). A failure is
161
- * marked by the payload-level `error` OR a block-level `isError`. The legacy
162
- * pre-rc.2 shape (no `message`) is tolerated as an empty payload it never
163
- * throws. Budget: 500 chars per line (the 12-line cap lives in
164
- * buildReviewRequest and is unchanged).
154
+ * V10-10 (P2-11) / A1 (audit P1-1): render one `[result]` evidence line from
155
+ * a tool-result event payload. The former read (`data.output`) targeted a
156
+ * field that does not exist on the rc.2 payload; the current contract covers
157
+ * BOTH rc.2 result shapes:
158
+ * - native `tool/result`: the outcome lives in `message.content`
159
+ * tool-result blocks (inner text blocks joined); a failure is marked by
160
+ * the payload-level `error` OR a block-level `isError`;
161
+ * - PTC `tool/ptc-dispatch` settle: the outcome lives at the TOP level
162
+ * `content` is the logged ContentBlock list and `isError` the flag; there
163
+ * is no `message` wrapper. (Before A1 this shape rendered an empty line,
164
+ * so every PTC session's evidence block starved while its plan prompt
165
+ * still demanded evidence.)
166
+ * The legacy pre-rc.2 shape (neither `message` nor a PTC settle marker) is
167
+ * tolerated as an empty payload — it never throws. `identity`, when given, is
168
+ * the dispatched tool's name + raw arguments, prepended so a settled dispatch
169
+ * keeps the call identity its `[call]` line would have had. Budget: 500 chars
170
+ * per line (the 12-line cap lives in buildReviewRequest and is unchanged).
165
171
  */
166
- export declare function renderToolResultLine(data: unknown): string;
172
+ export declare function renderToolResultLine(data: unknown, identity?: {
173
+ name: string;
174
+ argsRaw: string;
175
+ }): string;
167
176
  export declare function buildReviewRequest(session: Session, kind: ReviewKind, signal: {
168
177
  toolCalls: number;
169
178
  userChars: number;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-review",
3
3
  "description": "Background review orchestration (community build)",
4
- "version": "0.4.0",
4
+ "version": "0.5.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,9 +27,9 @@
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
29
  "@deepseek-ai/schemastery": "^3.18.1",
30
- "@lmzhen/dsh-evolution-approval": "^0.4.0",
31
- "@lmzhen/dsh-evolution-core": "^0.4.0",
32
- "@lmzhen/dsh-evolution-plan-validator": "^0.4.0"
30
+ "@lmzhen/dsh-evolution-approval": "^0.5.0",
31
+ "@lmzhen/dsh-evolution-core": "^0.5.0",
32
+ "@lmzhen/dsh-evolution-plan-validator": "^0.5.0"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "@deepseek-ai/cordis": "^4.0.1",
@@ -37,8 +37,8 @@
37
37
  "@deepseek-ai/dsh-llm": "^0.1.5-rc.2",
38
38
  "@deepseek-ai/dsh-session": "^0.1.5-rc.2",
39
39
  "@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
40
- "@lmzhen/dsh-evolution-state": "^0.4.0",
41
- "@lmzhen/dsh-evolution-policy": "^0.4.0"
40
+ "@lmzhen/dsh-evolution-state": "^0.5.0",
41
+ "@lmzhen/dsh-evolution-policy": "^0.5.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@deepseek-ai/dsh-agent": "^0.1.5-rc.2",
@@ -48,10 +48,10 @@
48
48
  "@deepseek-ai/dsh-session-persistence": "^0.1.5-rc.2",
49
49
  "@deepseek-ai/dsh-session-persistence-jsonl": "^0.1.5-rc.2",
50
50
  "@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
51
- "@lmzhen/dsh-evolution-approval": "^0.4.0",
52
- "@lmzhen/dsh-evolution-core": "^0.4.0",
53
- "@lmzhen/dsh-evolution-curator": "^0.4.0",
54
- "@lmzhen/dsh-evolution-plan-validator": "^0.4.0",
55
- "@lmzhen/dsh-evolution-state": "^0.4.0"
51
+ "@lmzhen/dsh-evolution-approval": "^0.5.0",
52
+ "@lmzhen/dsh-evolution-core": "^0.5.0",
53
+ "@lmzhen/dsh-evolution-curator": "^0.5.0",
54
+ "@lmzhen/dsh-evolution-plan-validator": "^0.5.0",
55
+ "@lmzhen/dsh-evolution-state": "^0.5.0"
56
56
  }
57
57
  }