@hadooppei/hwcode 1.0.19 → 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.
@@ -7,7 +7,7 @@ import { parentPort, workerData } from "node:worker_threads";
7
7
  import { KNOWLEDGE_RUNTIME_DEFAULTS } from "../runtime/defaults.ts";
8
8
  import { userRuntimePaths } from "../runtime/paths.ts";
9
9
  import { parseKnowledgeReviewEnvelope } from "./extractor.ts";
10
- import { type KnowledgeReviewStatus, writeKnowledgeReviewStatus } from "./review-status.ts";
10
+ import { readKnowledgeReviewStatus, type KnowledgeReviewStatus, writeKnowledgeReviewStatus } from "./review-status.ts";
11
11
  import { sanitizeKnowledgeText } from "./sanitize.ts";
12
12
  import { findNextReviewTask } from "./session-scanner.ts";
13
13
  import { commitKnowledgeReview, ensureKnowledgeDirectories, loadCurrentManifest } from "./store.ts";
@@ -59,21 +59,44 @@ function publishReviewStatus(): void {
59
59
  }
60
60
 
61
61
  function boundedError(error: unknown): string {
62
- return sanitizeKnowledgeText(error instanceof Error ? error.message : String(error)).slice(0, 1_000);
62
+ return sanitizeKnowledgeText(error instanceof Error ? error.message : String(error))
63
+ .slice(0, KNOWLEDGE_RUNTIME_DEFAULTS.review.maxFailureDetailChars);
64
+ }
65
+
66
+ function clearSupersededReviewState(task: KnowledgeReviewTask): void {
67
+ if (!reviewStatus) return;
68
+ for (const field of ["blockedReviews", "reviewFailures"] as const) {
69
+ const records = reviewStatus[field];
70
+ if (!records) continue;
71
+ for (const [reviewKey, record] of Object.entries(records)) {
72
+ if (record.sessionKey === task.sessionKey && reviewKey !== task.reviewKey) {
73
+ delete records[reviewKey];
74
+ reviewAttempts.delete(reviewKey);
75
+ }
76
+ }
77
+ if (Object.keys(records).length === 0) reviewStatus[field] = undefined;
78
+ }
63
79
  }
64
80
 
65
81
  function beginReview(task: KnowledgeReviewTask): void {
82
+ clearSupersededReviewState(task);
66
83
  activeAttempt = (reviewAttempts.get(task.reviewKey) ?? 0) + 1;
67
84
  reviewAttempts.set(task.reviewKey, activeAttempt);
68
85
  activeStartedAt = Date.now();
69
86
  clearTimeout(activeDeadlineTimer);
70
87
  activeDeadlineTimer = setTimeout(() => {
71
88
  if (activeTask?.requestId !== task.requestId) return;
72
- const error = "knowledge-review-result-timeout";
73
- finishReview(task, "failed", { error });
89
+ const error = [
90
+ `knowledge-review-result-timeout after ${KNOWLEDGE_RUNTIME_DEFAULTS.review.modelTimeoutMs + 5_000}ms`,
91
+ `reviewKey=${task.reviewKey}`,
92
+ `deltaChars=${task.delta.length}`,
93
+ `evidenceItems=${task.evidence?.length ?? 0}`,
94
+ `episodeItems=${task.episodes?.length ?? 0}`,
95
+ ].join("; ");
96
+ const terminal = finishReview(task, "failed", { error });
74
97
  activeTask = undefined;
75
98
  send({ type: "review_cancel", requestId: task.requestId, reason: error });
76
- send({ type: "review_failed", requestId: task.requestId, error });
99
+ send({ type: "review_failed", requestId: task.requestId, error, terminal });
77
100
  }, KNOWLEDGE_RUNTIME_DEFAULTS.review.modelTimeoutMs + 5_000);
78
101
  activeDeadlineTimer.unref();
79
102
  if (reviewStatus) {
@@ -93,24 +116,59 @@ function finishReview(
93
116
  task: KnowledgeReviewTask,
94
117
  outcome: "saved" | "failed",
95
118
  detail: { generationId?: string; result?: PersistKnowledgeResult; error?: unknown },
96
- ): void {
119
+ ): boolean {
97
120
  clearTimeout(activeDeadlineTimer);
98
121
  activeDeadlineTimer = undefined;
99
- if (!reviewStatus) return;
122
+ if (!reviewStatus) return false;
123
+ const error = detail.error === undefined ? undefined : boundedError(detail.error);
124
+ const exhausted = outcome === "failed" && activeAttempt >= KNOWLEDGE_RUNTIME_DEFAULTS.review.maxAttempts;
125
+ const completedAt = new Date().toISOString();
126
+ const durationMs = Math.max(0, Date.now() - activeStartedAt);
100
127
  reviewStatus.activeReview = undefined;
101
128
  reviewStatus.lastReview = {
102
129
  reviewKey: task.reviewKey,
103
130
  sessionKey: task.sessionKey,
104
131
  attempt: activeAttempt,
105
132
  outcome,
106
- completedAt: new Date().toISOString(),
107
- durationMs: Math.max(0, Date.now() - activeStartedAt),
133
+ completedAt,
134
+ durationMs,
108
135
  generationId: detail.generationId,
109
136
  result: detail.result,
110
- error: detail.error === undefined ? undefined : boundedError(detail.error),
137
+ error,
111
138
  };
112
- if (outcome === "saved") reviewAttempts.delete(task.reviewKey);
139
+ if (outcome === "saved") {
140
+ reviewAttempts.delete(task.reviewKey);
141
+ delete reviewStatus.reviewFailures?.[task.reviewKey];
142
+ clearSupersededReviewState(task);
143
+ } else {
144
+ const reviewFailures = reviewStatus.reviewFailures ?? {};
145
+ const prior = reviewFailures[task.reviewKey];
146
+ reviewFailures[task.reviewKey] = {
147
+ sessionKey: task.sessionKey,
148
+ sessionFileHash: task.sessionFileHash,
149
+ attempts: [...(prior?.attempts ?? []), {
150
+ attempt: activeAttempt,
151
+ failedAt: completedAt,
152
+ durationMs,
153
+ error: error ?? "unknown-review-failure",
154
+ }].slice(-KNOWLEDGE_RUNTIME_DEFAULTS.review.maxAttempts),
155
+ };
156
+ reviewStatus.reviewFailures = Object.fromEntries(Object.entries(reviewFailures).slice(-100));
157
+ }
158
+ if (exhausted) {
159
+ const blockedReviews = reviewStatus.blockedReviews ?? {};
160
+ blockedReviews[task.reviewKey] = {
161
+ reviewKey: task.reviewKey,
162
+ sessionKey: task.sessionKey,
163
+ sessionFileHash: task.sessionFileHash,
164
+ blockedAt: new Date().toISOString(),
165
+ attempts: activeAttempt,
166
+ error: error ?? "unknown-review-failure",
167
+ };
168
+ reviewStatus.blockedReviews = Object.fromEntries(Object.entries(blockedReviews).slice(-100));
169
+ }
113
170
  publishReviewStatus();
171
+ return exhausted;
114
172
  }
115
173
 
116
174
  function deferReview(task: KnowledgeReviewTask, reason: string): void {
@@ -155,7 +213,12 @@ function scheduleElection(delay = Math.floor(Math.random() * 500)): void {
155
213
  }
156
214
 
157
215
  function stepDown(reason: string): void {
158
- if (activeTask) send({ type: "review_cancel", requestId: activeTask.requestId, reason });
216
+ if (activeTask) {
217
+ const attempts = reviewAttempts.get(activeTask.reviewKey) ?? 1;
218
+ if (attempts <= 1) reviewAttempts.delete(activeTask.reviewKey);
219
+ else reviewAttempts.set(activeTask.reviewKey, attempts - 1);
220
+ send({ type: "review_cancel", requestId: activeTask.requestId, reason });
221
+ }
159
222
  if (reviewStatus) {
160
223
  reviewStatus.state = "stepped-down";
161
224
  reviewStatus.stepDownReason = reason;
@@ -260,8 +323,16 @@ function becomeLeader(server: Server): void {
260
323
  scheduleElection();
261
324
  });
262
325
  writeLeaderMetadata(electedAt);
326
+ const priorStatus = readKnowledgeReviewStatus(home);
327
+ reviewAttempts.clear();
328
+ for (const [reviewKey, failure] of Object.entries(priorStatus?.reviewFailures ?? {})) {
329
+ const attempt = Math.max(0, ...failure.attempts.map((item) => item.attempt));
330
+ if (attempt > 0) reviewAttempts.set(reviewKey, attempt);
331
+ }
263
332
  reviewStatus = {
264
333
  version: 1, protocolVersion: 3, state: "leader", pid: process.pid, leaderToken, electedAt, updatedAt: electedAt,
334
+ blockedReviews: priorStatus?.blockedReviews,
335
+ reviewFailures: priorStatus?.reviewFailures,
265
336
  };
266
337
  publishReviewStatus();
267
338
  send({ type: "leadership", state: "leader", leaderToken });
@@ -296,7 +367,8 @@ async function scanForReview(): Promise<void> {
296
367
  }
297
368
  try {
298
369
  const manifest = loadCurrentManifest(home);
299
- const task = findNextReviewTask(sessionsRoot, manifest.reviews);
370
+ const blockedReviewKeys = new Set(Object.keys(reviewStatus?.blockedReviews ?? {}));
371
+ const task = findNextReviewTask(sessionsRoot, manifest.reviews, Date.now(), blockedReviewKeys);
300
372
  if (!task) return;
301
373
  activeTask = task;
302
374
  beginReview(task);
@@ -315,13 +387,14 @@ async function scanForReview(): Promise<void> {
315
387
  }
316
388
  send({ type: "review_request", leaderToken, requestId: task.requestId, task });
317
389
  } catch (error) {
318
- if (activeTask) finishReview(activeTask, "failed", { error });
319
- else if (reviewStatus) {
390
+ const failedTask = activeTask;
391
+ const terminal = failedTask ? finishReview(failedTask, "failed", { error }) : true;
392
+ if (!failedTask && reviewStatus) {
320
393
  reviewStatus.lastScanError = { at: new Date().toISOString(), error: boundedError(error) };
321
394
  publishReviewStatus();
322
395
  }
323
396
  activeTask = undefined;
324
- send({ type: "review_failed", error: boundedError(error) });
397
+ send({ type: "review_failed", requestId: failedTask?.requestId, error: boundedError(error), terminal });
325
398
  }
326
399
  }
327
400
 
@@ -342,8 +415,8 @@ function handleReviewResult(message: Extract<KnowledgeWorkerInput, { type: "revi
342
415
  }
343
416
  committed = true;
344
417
  } catch (error) {
345
- finishReview(task, "failed", { error });
346
- send({ type: "review_failed", requestId: task.requestId, error: boundedError(error) });
418
+ const terminal = finishReview(task, "failed", { error });
419
+ send({ type: "review_failed", requestId: task.requestId, error: boundedError(error), terminal });
347
420
  } finally {
348
421
  activeTask = undefined;
349
422
  if (committed) queueMicrotask(() => void scanForReview());
@@ -3,6 +3,16 @@ const BEARER_RE = /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/giu;
3
3
  const JWT_RE = /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/gu;
4
4
  const SECRET_ASSIGNMENT_RE = /\b(access[_-]?key|secret(?:[_-]?(?:access|key))?|password|passwd|token|credential|client[_-]?secret)\b(\s*[:=]\s*)[^\s,;]+/giu;
5
5
  const URL_CREDENTIAL_RE = /(https?:\/\/)[^\s/@:]+:[^\s/@]+@/giu;
6
+ const INJECTED_SKILL_BLOCK_RE = /<skill\b[^>]*>[\s\S]*?<\/skill>/giu;
7
+ const INJECTED_SKILL_MARKER_RE = /<skill\b[^>]*>/iu;
8
+
9
+ export function containsInjectedSkill(value: string): boolean {
10
+ return INJECTED_SKILL_MARKER_RE.test(value);
11
+ }
12
+
13
+ export function stripInjectedSkillBlocks(value: string): string {
14
+ return value.replace(INJECTED_SKILL_BLOCK_RE, "");
15
+ }
6
16
 
7
17
  export function sanitizeKnowledgeText(value: string): string {
8
18
  return value
@@ -10,7 +10,7 @@ import { KNOWLEDGE_RUNTIME_DEFAULTS } from "../runtime/defaults.ts";
10
10
  import { canonicalizeDirectory } from "../working-directory.ts";
11
11
  import { buildKnowledgeEpisodes, buildKnowledgeEvidence } from "./evidence.ts";
12
12
  import { knowledgeDeltaDigest } from "./extractor.ts";
13
- import { sanitizeKnowledgeText } from "./sanitize.ts";
13
+ import { containsInjectedSkill, sanitizeKnowledgeText, stripInjectedSkillBlocks } from "./sanitize.ts";
14
14
  import { projectKnowledgeKey } from "./store.ts";
15
15
  import type { KnowledgeReviewCursor, KnowledgeReviewTask } from "./types.ts";
16
16
 
@@ -22,7 +22,6 @@ interface ReviewPiece {
22
22
  }
23
23
 
24
24
  const FAILURE_EVIDENCE_RE = /(?:\b(?:error|failed|failure|invalid|unsupported|mistyp(?:e|ed)|typo)\b|not supported|失败|错误|不支持|无效)/iu;
25
- const INJECTED_SKILL_RE = /<skill\b[^>]*>[\s\S]*?<\/skill>/giu;
26
25
  const SAFE_KNOWLEDGE_ID_RE = /^[a-z0-9][a-z0-9-]{0,79}$/u;
27
26
  const MAX_RECALL_QUERY_CHARS = 4_000;
28
27
  const MAX_LOADED_KNOWLEDGE_IDS = 8;
@@ -54,8 +53,13 @@ function reviewPiece(entry: SessionEntry, index: number): ReviewPiece | undefine
54
53
  if (entry.type !== "message" || !entry.message || typeof entry.message !== "object") return undefined;
55
54
  const message = entry.message as unknown as Record<string, unknown>;
56
55
  const role = typeof message.role === "string" ? message.role : "message";
57
- const content = sanitizeKnowledgeText(textContent(message.content).replace(INJECTED_SKILL_RE, "[loaded skill omitted]"));
56
+ const rawContent = textContent(message.content);
57
+ const injectedContext = role === "user" && containsInjectedSkill(rawContent);
58
+ const content = sanitizeKnowledgeText(injectedContext ? stripInjectedSkillBlocks(rawContent) : rawContent);
58
59
  if (!content) return undefined;
60
+ if (injectedContext) {
61
+ return { index, priority: 3, category: "assistant", text: `[injected_context]\n${content.slice(0, 4_000)}` };
62
+ }
59
63
  const priority = role === "user" ? 0 : role === "toolResult" && FAILURE_EVIDENCE_RE.test(content) ? 2
60
64
  : role === "toolResult" ? 4 : 3;
61
65
  const cap = role === "user" ? 8_000 : role === "toolResult" ? 1_500 : 4_000;
@@ -63,8 +67,39 @@ function reviewPiece(entry: SessionEntry, index: number): ReviewPiece | undefine
63
67
  return { index, priority, category, text: `[${role}]\n${content.slice(0, cap)}` };
64
68
  }
65
69
 
66
- function buildDelta(entries: SessionEntry[]): string {
67
- const pieces = entries.map(reviewPiece).filter((piece): piece is ReviewPiece => Boolean(piece));
70
+ function entryId(entry: SessionEntry): string | undefined {
71
+ const value = (entry as unknown as Record<string, unknown>).id;
72
+ return typeof value === "string" ? value : undefined;
73
+ }
74
+
75
+ function knowledgeLookupResultEntryIds(entries: SessionEntry[]): Set<string> {
76
+ const lookupCalls = new Set<string>();
77
+ const resultEntries = new Set<string>();
78
+ for (const entry of entries) {
79
+ const message = messageRecord(entry);
80
+ const content = message?.content;
81
+ if (Array.isArray(content)) {
82
+ for (const raw of content) {
83
+ if (!raw || typeof raw !== "object") continue;
84
+ const item = raw as Record<string, unknown>;
85
+ if (item.type === "toolCall" && item.name === "hwcode_knowledge_lookup" && typeof item.id === "string") {
86
+ lookupCalls.add(item.id);
87
+ }
88
+ }
89
+ }
90
+ if (message?.role === "toolResult" && typeof message.toolCallId === "string" && lookupCalls.has(message.toolCallId)) {
91
+ const id = entryId(entry);
92
+ if (id) resultEntries.add(id);
93
+ }
94
+ }
95
+ return resultEntries;
96
+ }
97
+
98
+ function buildDelta(entries: SessionEntry[], excludedEntryIds: ReadonlySet<string> = new Set()): string {
99
+ const pieces = entries.map((entry, index) => {
100
+ const id = entryId(entry);
101
+ return id && excludedEntryIds.has(id) ? undefined : reviewPiece(entry, index);
102
+ }).filter((piece): piece is ReviewPiece => Boolean(piece));
68
103
  const selected: ReviewPiece[] = [];
69
104
  const categoryRemaining: Record<ReviewPiece["category"], number> = {
70
105
  user: 14_000, workflow: 6_000, summary: 4_000, assistant: 8_000, tool: 4_000,
@@ -87,8 +122,11 @@ function buildDelta(entries: SessionEntry[]): string {
87
122
  .slice(0, KNOWLEDGE_RUNTIME_DEFAULTS.review.maxDeltaChars);
88
123
  }
89
124
 
90
- function buildValidationContext(entries: SessionEntry[]): string {
91
- const pieces = entries.map(reviewPiece).filter((piece): piece is ReviewPiece => Boolean(piece));
125
+ function buildValidationContext(entries: SessionEntry[], excludedEntryIds: ReadonlySet<string> = new Set()): string {
126
+ const pieces = entries.map((entry, index) => {
127
+ const id = entryId(entry);
128
+ return id && excludedEntryIds.has(id) ? undefined : reviewPiece(entry, index);
129
+ }).filter((piece): piece is ReviewPiece => Boolean(piece));
92
130
  const latestWorkflow = pieces.filter((piece) => piece.category === "workflow").at(-1);
93
131
  const evidence = pieces.filter((piece) => (
94
132
  (piece.category === "tool" || piece.category === "assistant" || piece.category === "summary")
@@ -183,7 +221,9 @@ function buildRecallQuery(entries: SessionEntry[], ids: string[]): string {
183
221
  for (const entry of entries.slice().reverse()) {
184
222
  const message = messageRecord(entry);
185
223
  if (!message || message.role !== "user") continue;
186
- const text = compactRecallValue(textContent(message.content).replace(INJECTED_SKILL_RE, ""), 800);
224
+ const raw = textContent(message.content);
225
+ if (containsInjectedSkill(raw)) continue;
226
+ const text = compactRecallValue(raw, 800);
187
227
  if (text) lines.push(`User request: ${text}`);
188
228
  if (lines.filter((line) => line.startsWith("User request:")).length >= 2) break;
189
229
  }
@@ -245,10 +285,13 @@ export function readReviewTask(
245
285
  const evidenceWindow = cursorIndex >= 0 ? branch.slice(Math.max(0, cursorIndex - 40)) : pending;
246
286
  const evidence = buildKnowledgeEvidence(evidenceWindow).filter((item) => pendingEntryIds.has(item.entryId));
247
287
  const episodes = buildKnowledgeEpisodes(evidence);
248
- const delta = buildDelta(pending);
288
+ const lookupResultEntryIds = knowledgeLookupResultEntryIds(branch);
289
+ const delta = buildDelta(pending, lookupResultEntryIds);
249
290
  const loadedIds = loadedKnowledgeIds(pending);
250
291
  const recallQuery = buildRecallQuery(pending, loadedIds);
251
- const context = cursorIndex >= 0 ? buildValidationContext(branch.slice(0, cursorIndex + 1)) : "";
292
+ const context = cursorIndex >= 0
293
+ ? buildValidationContext(branch.slice(0, cursorIndex + 1), lookupResultEntryIds)
294
+ : "";
252
295
  const firstEntryId = pending[0].id;
253
296
  const lastEntryId = pending[pending.length - 1].id;
254
297
  const deltaDigest = knowledgeDeltaDigest([
@@ -272,6 +315,7 @@ export function findNextReviewTask(
272
315
  sessionsRoot: string,
273
316
  reviews: Record<string, KnowledgeReviewCursor>,
274
317
  now = Date.now(),
318
+ excludedReviewKeys: ReadonlySet<string> = new Set(),
275
319
  ): KnowledgeReviewTask | undefined {
276
320
  const files = discoverSessionFiles(sessionsRoot).map((path) => {
277
321
  try { return { path, mtimeMs: statSync(path).mtimeMs }; } catch { return undefined; }
@@ -283,7 +327,7 @@ export function findNextReviewTask(
283
327
  let task = readReviewTask(file.path, cursor, now);
284
328
  const stableCursor = task ? reviews[task.sessionKey] : undefined;
285
329
  if (task && stableCursor && stableCursor !== cursor) task = readReviewTask(file.path, stableCursor, now);
286
- if (task) return task;
330
+ if (task && !excludedReviewKeys.has(task.reviewKey)) return task;
287
331
  }
288
332
  return undefined;
289
333
  }
@@ -13,7 +13,7 @@ export type KnowledgeWorkerOutput =
13
13
  | { type: "review_request"; leaderToken: string; requestId: string; task: KnowledgeReviewTask }
14
14
  | { type: "review_cancel"; requestId: string; reason: string }
15
15
  | { type: "review_saved"; requestId: string; generationId?: string; result: PersistKnowledgeResult }
16
- | { type: "review_failed"; requestId?: string; error: string }
16
+ | { type: "review_failed"; requestId?: string; error: string; terminal?: boolean }
17
17
  | { type: "generation_changed"; generationId: string };
18
18
 
19
19
  export interface CoordinatorBroadcast {
@@ -31,14 +31,18 @@ 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,
37
+ maxAttempts: 2,
38
+ maxFailureDetailChars: 4_000,
35
39
  maxDeltaChars: 12_000,
36
40
  maxPriorContextChars: 4_000,
37
41
  maxNarrativeChars: 6_000,
38
42
  maxEvidenceItems: 24,
39
43
  maxEvidenceChars: 12_000,
40
44
  mandatoryEpisodeScore: 45,
41
- maxExistingContextItems: 5,
45
+ maxExistingContextItems: 8,
42
46
  maxExistingBodyItems: 2,
43
47
  maxExistingBodyChars: 600,
44
48
  maxCandidates: 3,
@@ -18,6 +18,25 @@ export interface ProcessResult {
18
18
  spawnErrorCode?: string;
19
19
  }
20
20
 
21
+ const BRACKETED_PROVIDER_ERROR_RE = /\[(?:USE|CLI|AUTH|HTTP|SERVER|SYSTEM|SDK)_ERROR\][^\r\n]*/iu;
22
+ const STRUCTURED_PROVIDER_FAILURE_RE = /"kind"\s*:\s*"Status"[\s\S]{0,500}"status"\s*:\s*"(?:failure|failed|error)"/iu;
23
+ const STRUCTURED_ERROR_CODE_RE = /"(?:errorCode|error_code|code)"\s*:\s*(?:"[^"\r\n]+"|\d+)/iu;
24
+
25
+ /** Detect CLIs that report an API failure in output while incorrectly exiting with code 0. */
26
+ export function cloudProviderFailureReason(stdout: string, stderr: string): string | undefined {
27
+ const output = `${stderr}\n${stdout}`.trim();
28
+ if (!output) return undefined;
29
+ const bracketed = BRACKETED_PROVIDER_ERROR_RE.exec(output)?.[0]?.trim();
30
+ if (bracketed) return bracketed.slice(0, 2_000);
31
+ if (STRUCTURED_PROVIDER_FAILURE_RE.test(output) && STRUCTURED_ERROR_CODE_RE.test(output)) {
32
+ const status = STRUCTURED_PROVIDER_FAILURE_RE.exec(output)?.[0];
33
+ const code = STRUCTURED_ERROR_CODE_RE.exec(output)?.[0];
34
+ const message = /"message"\s*:\s*"([^"\r\n]+)"/iu.exec(output)?.[1];
35
+ return [status, code, message].filter(Boolean).join("; ").slice(0, 2_000);
36
+ }
37
+ return undefined;
38
+ }
39
+
21
40
  function captureOutput(current: string, chunk: Buffer): string {
22
41
  if (Buffer.byteLength(current, "utf8") >= MAX_CAPTURE_BYTES) return current;
23
42
  const remaining = MAX_CAPTURE_BYTES - Buffer.byteLength(current, "utf8");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hadooppei/hwcode",
3
- "version": "1.0.19",
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": {