@hadooppei/hwcode 1.0.20 → 1.0.22

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.
@@ -81,8 +81,11 @@ function boundedExcerpt(value, limit = 900) {
81
81
  if (clean.length <= limit)
82
82
  return clean;
83
83
  const lines = clean.split("\n");
84
- const significant = lines.filter((line) => FAILURE_RE.test(line) || VERIFICATION_RE.test(line));
85
- const selected = [...lines.slice(0, 3), ...significant.slice(0, 5), ...lines.slice(-3)];
84
+ const priority = (line) => REVERSAL_RE.test(line) ? 0
85
+ : FAILURE_RE.test(line) && VERIFICATION_RE.test(line) ? 1
86
+ : FAILURE_RE.test(line) ? 2 : 3;
87
+ const significant = lines.filter((line) => (REVERSAL_RE.test(line) || FAILURE_RE.test(line) || VERIFICATION_RE.test(line))).sort((left, right) => priority(left) - priority(right));
88
+ const selected = [...lines.slice(0, 2), ...significant.slice(0, 7), ...lines.slice(-2)];
86
89
  return [...new Set(selected)].join("\n").slice(0, limit);
87
90
  }
88
91
  function compactArguments(args) {
@@ -152,10 +155,8 @@ function entryIdOf(entry, index) {
152
155
  function evidenceId(entryId, suffix) {
153
156
  return `ev-${digest(`${entryId}\0${suffix}`)}`;
154
157
  }
155
- function outcomeFor(message, text) {
156
- if (message.isError === true || FAILURE_RE.test(text))
157
- return "failure";
158
- return "success";
158
+ function outcomeFor(message) {
159
+ return message.isError === true ? "failure" : "success";
159
160
  }
160
161
  function userKind(text) {
161
162
  return CORRECTION_RE.test(text) ? "user-correction" : "user-directive";
@@ -206,11 +207,11 @@ export function buildKnowledgeEvidence(entries) {
206
207
  // lookup result as verification would let knowledge reinforce itself merely by loading it.
207
208
  if (call?.name === "hwcode_knowledge_lookup")
208
209
  return;
209
- const outcome = outcomeFor(message, text);
210
+ const outcome = outcomeFor(message);
210
211
  const callText = call ? `[call] ${call.name}: ${compactArguments(call.args)}\n` : "";
211
212
  evidence.push({
212
213
  id: evidenceId(entryId, toolCallId || "tool"), entryId, timestamp,
213
- kind: outcome === "failure" ? "tool-failure" : VERIFICATION_RE.test(text) ? "verification" : "tool-success",
214
+ kind: outcome === "failure" ? "tool-failure" : "tool-success",
214
215
  subject: evidenceSubject(call, text), operation: operationName(call), outcome,
215
216
  excerpt: boundedExcerpt(`${callText}[result] ${text}`),
216
217
  });
@@ -219,7 +220,7 @@ export function buildKnowledgeEvidence(entries) {
219
220
  if (role === "assistant" && text && (FAILURE_RE.test(text) || VERIFICATION_RE.test(text) || REVERSAL_RE.test(text))) {
220
221
  evidence.push({
221
222
  id: evidenceId(entryId, "assistant"), entryId, timestamp,
222
- kind: VERIFICATION_RE.test(text) ? "verification" : "assistant-claim",
223
+ kind: "assistant-claim",
223
224
  subject: "Assistant diagnosis or conclusion", outcome: "unknown", excerpt: boundedExcerpt(text, 800),
224
225
  });
225
226
  }
@@ -14,9 +14,11 @@ Treat failed, unsupported, invalid, or corrected operations as negative evidence
14
14
  Use storageHint "rule" only for one short imperative instruction of at most 240 characters. A rule must not contain incident narration, example resource names, IDs, timestamps, or evidence details. Use "topic" for multi-step SOPs and detailed experience.
15
15
  Rank knowledge by future time saved, repeated failures, hypothesis reversals, user corrections, verified resolution, reuse, and novelty. Do not favor the final task result merely because it completed the objective. Every supplied episode scoring 45 or higher must have an episodeDecisions entry, even when skipped.
16
16
  Relevant existing knowledge may be supplied. Cheap recall only proposes related items; it does not prove duplication. Compare execution environment, symptom, and root cause. Use action "reinforce" only when the exact target already covers every claim and there is no new content. Use "extend" for new compatible claims, "correct" when direct evidence disproves old content, "retire" when the whole target is invalid, and "add" for a distinct environment or root cause. Never turn lexical similarity into reinforcement.
17
+ Prefer the most specific existing topic that already covers a claim over a broad conventions or SOP topic.
17
18
  Scope may be "global" only for an explicit user directive intended across projects; otherwise use "project".
18
19
  The prior validation context is only for checking facts and contradictions; do not persist it again unless the new delta independently reinforces it. Prefer accumulated workflow successfulSteps and failedApproaches over assistant narration.
19
20
  Every technical claim must have a stable claim key and cite evidenceIds from the supplied evidence ledger. Tool success, tool failure, and verification evidence outrank assistant narration. Do not support a service-wide or universal claim with one operation-specific failure. Evidence for an unrelated parameter error cannot support an API-version claim.
21
+ Keep each candidate atomic: put separable claim sets in separate candidates, omit any claim without direct cited evidence, and prioritize the highest-value supported candidates. One unsupported claim rejects its entire candidate.
20
22
  Each summary must be one complete sentence of at most 160 characters. Keep each topic body under 2200 characters. Escape line breaks and quotes inside JSON strings. Set durability to "stable" only after removing facts likely to change or be cheaply rediscovered. Return at most 3 candidates.
21
23
  The body must not repeat the title as a Markdown H1. The storage renderer supplies the H1. Replace any necessary example resource value with an obvious placeholder instead of a real project, namespace, image, network, or resource name.
22
24
  Return: {"episodeDecisions":[{"episodeId":"ep-id","decision":"add|reinforce|extend|correct|retire|skip","targetId":"existing-id or omitted","reason":"..."}],"candidates":[{"key":"stable semantic key","identityKey":"platform/runtime/problem/root-cause","targetId":"existing-id or null","episodeIds":["ep-id"],"title":"...","summary":"one complete sentence","keywords":["..."],"scope":"global|project","body":"markdown body or only the new section for extend","claims":[{"key":"stable claim key","text":"one precise claim","evidenceIds":["ev-id"]}],"evidenceIds":["ev-id"],"confidence":0.0,"storageHint":"rule|topic","action":"add|reinforce|extend|correct|retire","explicitUserDirective":false,"durability":"stable"}]}
@@ -30,7 +30,9 @@ export const KNOWLEDGE_RUNTIME_DEFAULTS = Object.freeze({
30
30
  intervalMs: 60_000,
31
31
  idleMs: 60_000,
32
32
  capabilityPollMs: 5_000,
33
+ requestTimeoutMs: 45_000,
33
34
  modelTimeoutMs: 180_000,
35
+ maxOutputTokens: 4_096,
34
36
  maxAttempts: 2,
35
37
  maxFailureDetailChars: 4_000,
36
38
  maxDeltaChars: 12_000,
@@ -39,7 +41,7 @@ export const KNOWLEDGE_RUNTIME_DEFAULTS = Object.freeze({
39
41
  maxEvidenceItems: 24,
40
42
  maxEvidenceChars: 12_000,
41
43
  mandatoryEpisodeScore: 45,
42
- maxExistingContextItems: 5,
44
+ maxExistingContextItems: 8,
43
45
  maxExistingBodyItems: 2,
44
46
  maxExistingBodyChars: 600,
45
47
  maxCandidates: 3,
@@ -1,4 +1,3 @@
1
- import { randomUUID } from "node:crypto";
2
1
  import { existsSync } from "node:fs";
3
2
  import { dirname } from "node:path";
4
3
  import { fileURLToPath } from "node:url";
@@ -54,7 +53,7 @@ function compactReviewEvidence(task: KnowledgeReviewTask): {
54
53
  let characters = 0;
55
54
  const add = (item: KnowledgeEvidence | undefined): void => {
56
55
  if (!item || seen.has(item.id) || selected.length >= KNOWLEDGE_RUNTIME_DEFAULTS.review.maxEvidenceItems) return;
57
- const compact = { ...item, excerpt: item.excerpt.slice(0, 500) };
56
+ const compact = { ...item, excerpt: item.excerpt.slice(0, 800) };
58
57
  const size = JSON.stringify(compact).length;
59
58
  if (characters + size > KNOWLEDGE_RUNTIME_DEFAULTS.review.maxEvidenceChars) return;
60
59
  selected.push(compact);
@@ -183,13 +182,20 @@ async function runReview(message: Extract<KnowledgeWorkerOutput, { type: "review
183
182
  seenRelated.add(entry.id);
184
183
  };
185
184
  const episodeMatches = (message.task.episodes ?? []).map((episode) => (
186
- matchKnowledge(`${episode.title}\n${episode.query}`, applicableCatalog, 2)
185
+ matchKnowledge(
186
+ `${episode.title}\n${episode.query}`,
187
+ applicableCatalog,
188
+ KNOWLEDGE_RUNTIME_DEFAULTS.review.maxExistingContextItems,
189
+ )
187
190
  ));
188
- for (const matches of episodeMatches.slice(0, 3)) addRelated(matches[0]);
191
+ for (const matches of episodeMatches) addRelated(matches[0]);
189
192
  for (const id of message.task.loadedKnowledgeIds ?? []) {
190
193
  addRelated(applicableCatalog.items.find((entry) => entry.id === id));
191
194
  }
192
- for (const matches of episodeMatches) for (const entry of matches.slice(1)) addRelated(entry);
195
+ const matchDepth = Math.max(0, ...episodeMatches.map((matches) => matches.length));
196
+ for (let rank = 1; rank < matchDepth; rank++) {
197
+ for (const matches of episodeMatches) addRelated(matches[rank]);
198
+ }
193
199
  for (const entry of matchKnowledge(
194
200
  message.task.recallQuery || message.task.delta,
195
201
  applicableCatalog,
@@ -219,7 +225,7 @@ async function runReview(message: Extract<KnowledgeWorkerOutput, { type: "review
219
225
  promptContext.evidence,
220
226
  promptContext.episodes,
221
227
  );
222
- review.diagnostic = [
228
+ const diagnostic = [
223
229
  `reviewKey=${message.task.reviewKey}`,
224
230
  `model=${ctx.model.provider}/${ctx.model.id}`,
225
231
  `promptChars=${prompt.length}`,
@@ -227,7 +233,12 @@ async function runReview(message: Extract<KnowledgeWorkerOutput, { type: "review
227
233
  `evidenceItems=${promptContext.evidence.length}`,
228
234
  `episodeItems=${promptContext.episodes.length}`,
229
235
  `existingItems=${related.length}`,
230
- ].join("; ");
236
+ ];
237
+ const requestStartedAt = Date.now();
238
+ const setRequestPhase = (phase: string): void => {
239
+ review!.diagnostic = [...diagnostic, `phase=${phase}`, `requestElapsedMs=${Date.now() - requestStartedAt}`].join("; ");
240
+ };
241
+ setRequestPhase("preparing-request");
231
242
  const response = await ctx.modelRegistry.complete(
232
243
  ctx.model,
233
244
  {
@@ -241,9 +252,24 @@ async function runReview(message: Extract<KnowledgeWorkerOutput, { type: "review
241
252
  timestamp: Date.now(),
242
253
  }],
243
254
  },
244
- { signal: controller.signal, reasoningEffort: "low", cacheRetention: "none", sessionId: randomUUID() },
255
+ {
256
+ signal: controller.signal,
257
+ reasoningEffort: "low",
258
+ cacheRetention: "none",
259
+ temperature: 0,
260
+ maxTokens: KNOWLEDGE_RUNTIME_DEFAULTS.review.maxOutputTokens,
261
+ timeoutMs: KNOWLEDGE_RUNTIME_DEFAULTS.review.requestTimeoutMs,
262
+ maxRetries: 0,
263
+ onPayload: () => { setRequestPhase("awaiting-http-response"); },
264
+ onResponse: (providerResponse: { status: number }) => {
265
+ setRequestPhase(`streaming-response-http-${providerResponse.status}`);
266
+ },
267
+ },
245
268
  );
246
269
  if (controller.signal.aborted) throw new Error(review.abortReason ?? "knowledge-review-interrupted");
270
+ if (response.stopReason === "error" || response.stopReason === "aborted") {
271
+ throw new Error(response.errorMessage ?? `knowledge-review-model-${response.stopReason}`);
272
+ }
247
273
  const raw = response.content.filter((item): item is { type: "text"; text: string } => item.type === "text")
248
274
  .map((item) => item.text).join("\n");
249
275
  post({ type: "review_result", leaderToken: message.leaderToken, requestId: message.requestId, raw });
@@ -99,8 +99,13 @@ function boundedExcerpt(value: string, limit = 900): string {
99
99
  const clean = sanitizeKnowledgeText(value).replace(/\n{3,}/gu, "\n\n").trim();
100
100
  if (clean.length <= limit) return clean;
101
101
  const lines = clean.split("\n");
102
- const significant = lines.filter((line) => FAILURE_RE.test(line) || VERIFICATION_RE.test(line));
103
- const selected = [...lines.slice(0, 3), ...significant.slice(0, 5), ...lines.slice(-3)];
102
+ const priority = (line: string): number => REVERSAL_RE.test(line) ? 0
103
+ : FAILURE_RE.test(line) && VERIFICATION_RE.test(line) ? 1
104
+ : FAILURE_RE.test(line) ? 2 : 3;
105
+ const significant = lines.filter((line) => (
106
+ REVERSAL_RE.test(line) || FAILURE_RE.test(line) || VERIFICATION_RE.test(line)
107
+ )).sort((left, right) => priority(left) - priority(right));
108
+ const selected = [...lines.slice(0, 2), ...significant.slice(0, 7), ...lines.slice(-2)];
104
109
  return [...new Set(selected)].join("\n").slice(0, limit);
105
110
  }
106
111
 
@@ -163,9 +168,8 @@ function evidenceId(entryId: string, suffix: string): string {
163
168
  return `ev-${digest(`${entryId}\0${suffix}`)}`;
164
169
  }
165
170
 
166
- function outcomeFor(message: Record<string, unknown>, text: string): "success" | "failure" {
167
- if (message.isError === true || FAILURE_RE.test(text)) return "failure";
168
- return "success";
171
+ function outcomeFor(message: Record<string, unknown>): "success" | "failure" {
172
+ return message.isError === true ? "failure" : "success";
169
173
  }
170
174
 
171
175
  function userKind(text: string): KnowledgeEvidenceKind {
@@ -215,11 +219,11 @@ export function buildKnowledgeEvidence(entries: SessionEntry[]): KnowledgeEviden
215
219
  // Recalled knowledge is comparison context, not fresh execution evidence. Treating a
216
220
  // lookup result as verification would let knowledge reinforce itself merely by loading it.
217
221
  if (call?.name === "hwcode_knowledge_lookup") return;
218
- const outcome = outcomeFor(message, text);
222
+ const outcome = outcomeFor(message);
219
223
  const callText = call ? `[call] ${call.name}: ${compactArguments(call.args)}\n` : "";
220
224
  evidence.push({
221
225
  id: evidenceId(entryId, toolCallId || "tool"), entryId, timestamp,
222
- kind: outcome === "failure" ? "tool-failure" : VERIFICATION_RE.test(text) ? "verification" : "tool-success",
226
+ kind: outcome === "failure" ? "tool-failure" : "tool-success",
223
227
  subject: evidenceSubject(call, text), operation: operationName(call), outcome,
224
228
  excerpt: boundedExcerpt(`${callText}[result] ${text}`),
225
229
  });
@@ -228,7 +232,7 @@ export function buildKnowledgeEvidence(entries: SessionEntry[]): KnowledgeEviden
228
232
  if (role === "assistant" && text && (FAILURE_RE.test(text) || VERIFICATION_RE.test(text) || REVERSAL_RE.test(text))) {
229
233
  evidence.push({
230
234
  id: evidenceId(entryId, "assistant"), entryId, timestamp,
231
- kind: VERIFICATION_RE.test(text) ? "verification" : "assistant-claim",
235
+ kind: "assistant-claim",
232
236
  subject: "Assistant diagnosis or conclusion", outcome: "unknown", excerpt: boundedExcerpt(text, 800),
233
237
  });
234
238
  }
@@ -33,9 +33,11 @@ Treat failed, unsupported, invalid, or corrected operations as negative evidence
33
33
  Use storageHint "rule" only for one short imperative instruction of at most 240 characters. A rule must not contain incident narration, example resource names, IDs, timestamps, or evidence details. Use "topic" for multi-step SOPs and detailed experience.
34
34
  Rank knowledge by future time saved, repeated failures, hypothesis reversals, user corrections, verified resolution, reuse, and novelty. Do not favor the final task result merely because it completed the objective. Every supplied episode scoring 45 or higher must have an episodeDecisions entry, even when skipped.
35
35
  Relevant existing knowledge may be supplied. Cheap recall only proposes related items; it does not prove duplication. Compare execution environment, symptom, and root cause. Use action "reinforce" only when the exact target already covers every claim and there is no new content. Use "extend" for new compatible claims, "correct" when direct evidence disproves old content, "retire" when the whole target is invalid, and "add" for a distinct environment or root cause. Never turn lexical similarity into reinforcement.
36
+ Prefer the most specific existing topic that already covers a claim over a broad conventions or SOP topic.
36
37
  Scope may be "global" only for an explicit user directive intended across projects; otherwise use "project".
37
38
  The prior validation context is only for checking facts and contradictions; do not persist it again unless the new delta independently reinforces it. Prefer accumulated workflow successfulSteps and failedApproaches over assistant narration.
38
39
  Every technical claim must have a stable claim key and cite evidenceIds from the supplied evidence ledger. Tool success, tool failure, and verification evidence outrank assistant narration. Do not support a service-wide or universal claim with one operation-specific failure. Evidence for an unrelated parameter error cannot support an API-version claim.
40
+ Keep each candidate atomic: put separable claim sets in separate candidates, omit any claim without direct cited evidence, and prioritize the highest-value supported candidates. One unsupported claim rejects its entire candidate.
39
41
  Each summary must be one complete sentence of at most 160 characters. Keep each topic body under 2200 characters. Escape line breaks and quotes inside JSON strings. Set durability to "stable" only after removing facts likely to change or be cheaply rediscovered. Return at most 3 candidates.
40
42
  The body must not repeat the title as a Markdown H1. The storage renderer supplies the H1. Replace any necessary example resource value with an obvious placeholder instead of a real project, namespace, image, network, or resource name.
41
43
  Return: {"episodeDecisions":[{"episodeId":"ep-id","decision":"add|reinforce|extend|correct|retire|skip","targetId":"existing-id or omitted","reason":"..."}],"candidates":[{"key":"stable semantic key","identityKey":"platform/runtime/problem/root-cause","targetId":"existing-id or null","episodeIds":["ep-id"],"title":"...","summary":"one complete sentence","keywords":["..."],"scope":"global|project","body":"markdown body or only the new section for extend","claims":[{"key":"stable claim key","text":"one precise claim","evidenceIds":["ev-id"]}],"evidenceIds":["ev-id"],"confidence":0.0,"storageHint":"rule|topic","action":"add|reinforce|extend|correct|retire","explicitUserDirective":false,"durability":"stable"}]}
@@ -31,7 +31,9 @@ export const KNOWLEDGE_RUNTIME_DEFAULTS = Object.freeze({
31
31
  intervalMs: 60_000,
32
32
  idleMs: 60_000,
33
33
  capabilityPollMs: 5_000,
34
+ requestTimeoutMs: 45_000,
34
35
  modelTimeoutMs: 180_000,
36
+ maxOutputTokens: 4_096,
35
37
  maxAttempts: 2,
36
38
  maxFailureDetailChars: 4_000,
37
39
  maxDeltaChars: 12_000,
@@ -40,7 +42,7 @@ export const KNOWLEDGE_RUNTIME_DEFAULTS = Object.freeze({
40
42
  maxEvidenceItems: 24,
41
43
  maxEvidenceChars: 12_000,
42
44
  mandatoryEpisodeScore: 45,
43
- maxExistingContextItems: 5,
45
+ maxExistingContextItems: 8,
44
46
  maxExistingBodyItems: 2,
45
47
  maxExistingBodyChars: 600,
46
48
  maxCandidates: 3,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hadooppei/hwcode",
3
- "version": "1.0.20",
3
+ "version": "1.0.22",
4
4
  "description": "A customizable terminal coding agent with local-model support and HWCode workflows.",
5
5
  "type": "module",
6
6
  "bin": {