@wrongstack/tools 0.295.1 → 0.296.2
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/dist/auto-proceed-loop-guard.d.ts +67 -0
- package/dist/auto-proceed-loop-guard.d.ts.map +1 -1
- package/dist/auto-proceed-loop-guard.js +95 -0
- package/dist/auto-proceed-loop-guard.js.map +2 -2
- package/dist/bash.js +53 -18
- package/dist/bash.js.map +2 -2
- package/dist/browser/index.js +17 -7
- package/dist/browser/index.js.map +2 -2
- package/dist/browser/network-guard-proxy.d.ts.map +1 -1
- package/dist/builtin.d.ts +2 -2
- package/dist/builtin.d.ts.map +1 -1
- package/dist/builtin.js +8937 -7250
- package/dist/builtin.js.map +4 -4
- package/dist/codebase-index/background-indexer.d.ts +28 -2
- package/dist/codebase-index/background-indexer.d.ts.map +1 -1
- package/dist/codebase-index/codebase-index-tool.d.ts.map +1 -1
- package/dist/codebase-index/dead-code-scan.d.ts +77 -0
- package/dist/codebase-index/dead-code-scan.d.ts.map +1 -0
- package/dist/codebase-index/index-service.d.ts +4 -3
- package/dist/codebase-index/index-service.d.ts.map +1 -1
- package/dist/codebase-index/index.d.ts +11 -8
- package/dist/codebase-index/index.d.ts.map +1 -1
- package/dist/codebase-index/index.js +5038 -3573
- package/dist/codebase-index/index.js.map +4 -4
- package/dist/codebase-index/indexer.d.ts +2 -0
- package/dist/codebase-index/indexer.d.ts.map +1 -1
- package/dist/codebase-index/parser-dispatch.d.ts +8 -0
- package/dist/codebase-index/parser-dispatch.d.ts.map +1 -0
- package/dist/codebase-index/project-server-cache.d.ts +11 -0
- package/dist/codebase-index/project-server-cache.d.ts.map +1 -0
- package/dist/codebase-index/project-server-client.d.ts +74 -0
- package/dist/codebase-index/project-server-client.d.ts.map +1 -0
- package/dist/codebase-index/project-server-endpoint.d.ts +24 -0
- package/dist/codebase-index/project-server-endpoint.d.ts.map +1 -0
- package/dist/codebase-index/project-server-protocol.d.ts +88 -0
- package/dist/codebase-index/project-server-protocol.d.ts.map +1 -0
- package/dist/codebase-index/project-server.d.ts +11 -0
- package/dist/codebase-index/project-server.d.ts.map +1 -0
- package/dist/codebase-index/project-server.js +5061 -0
- package/dist/codebase-index/project-server.js.map +7 -0
- package/dist/codebase-index/refs-extractor.d.ts +1 -0
- package/dist/codebase-index/refs-extractor.d.ts.map +1 -1
- package/dist/codebase-index/schema.d.ts +11 -0
- package/dist/codebase-index/schema.d.ts.map +1 -1
- package/dist/codebase-index/sqlite-runtime.d.ts +9 -0
- package/dist/codebase-index/sqlite-runtime.d.ts.map +1 -0
- package/dist/codebase-index/ts-parser.d.ts +5 -1
- package/dist/codebase-index/ts-parser.d.ts.map +1 -1
- package/dist/codebase-index/worker-protocol.d.ts +19 -1
- package/dist/codebase-index/worker-protocol.d.ts.map +1 -1
- package/dist/codebase-index/worker.js +3860 -3415
- package/dist/codebase-index/worker.js.map +4 -4
- package/dist/codebase-index/writer-admin.d.ts +16 -0
- package/dist/codebase-index/writer-admin.d.ts.map +1 -0
- package/dist/codebase-index/writer-bulk-insert.d.ts +25 -0
- package/dist/codebase-index/writer-bulk-insert.d.ts.map +1 -0
- package/dist/codebase-index/writer-graph-helpers.d.ts +52 -0
- package/dist/codebase-index/writer-graph-helpers.d.ts.map +1 -0
- package/dist/codebase-index/writer-graph-reader.d.ts +11 -0
- package/dist/codebase-index/writer-graph-reader.d.ts.map +1 -0
- package/dist/codebase-index/writer-helpers.d.ts +15 -0
- package/dist/codebase-index/writer-helpers.d.ts.map +1 -0
- package/dist/codebase-index/writer-pragmas.d.ts +3 -0
- package/dist/codebase-index/writer-pragmas.d.ts.map +1 -0
- package/dist/codebase-index/writer-ref-mapper.d.ts +11 -0
- package/dist/codebase-index/writer-ref-mapper.d.ts.map +1 -0
- package/dist/codebase-index/writer-schema.d.ts +7 -0
- package/dist/codebase-index/writer-schema.d.ts.map +1 -0
- package/dist/codebase-index/writer-search-helpers.d.ts +28 -0
- package/dist/codebase-index/writer-search-helpers.d.ts.map +1 -0
- package/dist/codebase-index/writer-store-pool.d.ts +45 -0
- package/dist/codebase-index/writer-store-pool.d.ts.map +1 -0
- package/dist/codebase-index/writer.d.ts +53 -88
- package/dist/codebase-index/writer.d.ts.map +1 -1
- package/dist/exec.js +53 -18
- package/dist/exec.js.map +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9318 -7635
- package/dist/index.js.map +4 -4
- package/dist/kanban-board-inputs.d.ts +34 -0
- package/dist/kanban-board-inputs.d.ts.map +1 -0
- package/dist/kanban-decomposition-actions.d.ts +4 -0
- package/dist/kanban-decomposition-actions.d.ts.map +1 -0
- package/dist/kanban-detail-actions.d.ts +3 -0
- package/dist/kanban-detail-actions.d.ts.map +1 -0
- package/dist/kanban-presence.d.ts +10 -0
- package/dist/kanban-presence.d.ts.map +1 -0
- package/dist/kanban-split-task-handler.d.ts +6 -0
- package/dist/kanban-split-task-handler.d.ts.map +1 -0
- package/dist/kanban-task-inputs.d.ts +73 -0
- package/dist/kanban-task-inputs.d.ts.map +1 -0
- package/dist/kanban-tool-results.d.ts +14 -0
- package/dist/kanban-tool-results.d.ts.map +1 -0
- package/dist/kanban-tool-schema.d.ts +5 -0
- package/dist/kanban-tool-schema.d.ts.map +1 -0
- package/dist/kanban-tool-types.d.ts +135 -0
- package/dist/kanban-tool-types.d.ts.map +1 -0
- package/dist/kanban.d.ts +2 -146
- package/dist/kanban.d.ts.map +1 -1
- package/dist/kanban.js +1030 -825
- package/dist/kanban.js.map +4 -4
- package/dist/pack.js +8936 -7250
- package/dist/pack.js.map +4 -4
- package/dist/plan.js +2 -2
- package/dist/plan.js.map +2 -2
- package/dist/process-registry-persistent.d.ts +1 -0
- package/dist/process-registry-persistent.d.ts.map +1 -1
- package/dist/ps-slash.js +53 -18
- package/dist/ps-slash.js.map +2 -2
- package/dist/replace.js +8 -0
- package/dist/replace.js.map +2 -2
- package/dist/session-kanban.d.ts.map +1 -1
- package/dist/session-kanban.js +15 -20
- package/dist/session-kanban.js.map +2 -2
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js +48 -6
- package/dist/task.js.map +2 -2
- package/dist/todo.d.ts.map +1 -1
- package/dist/todo.js +47 -5
- package/dist/todo.js.map +2 -2
- package/dist/tool-tier.js +8938 -7251
- package/dist/tool-tier.js.map +4 -4
- package/package.json +5 -5
- package/dist/shim/node-sqlite.d.ts +0 -3
- package/dist/shim/node-sqlite.d.ts.map +0 -1
|
@@ -155,5 +155,72 @@ export interface AutoProceedLoopGuard {
|
|
|
155
155
|
* feedPrompt(prompt);
|
|
156
156
|
* }
|
|
157
157
|
*/
|
|
158
|
+
export interface ContinuationAttemptTracker {
|
|
159
|
+
/** Record an attempt against a todo. Returns the new attempt count (≥ 1). */
|
|
160
|
+
recordAttempt(todoId: string): number;
|
|
161
|
+
/** How many times continuation has been attempted for this todo (0 = never). */
|
|
162
|
+
getAttempts(todoId: string): number;
|
|
163
|
+
/** Mark a todo as "skipped" — a later attempt tried advancing past it. */
|
|
164
|
+
markSkipped(todoId: string): void;
|
|
165
|
+
/** True when this todo has been skipped due to no board progress. */
|
|
166
|
+
isSkipped(todoId: string): boolean;
|
|
167
|
+
/** Reset every tracked todo (call on manual user input). */
|
|
168
|
+
reset(): void;
|
|
169
|
+
/** Read-only snapshot for diagnostic use. */
|
|
170
|
+
snapshot(): Record<string, {
|
|
171
|
+
attempts: number;
|
|
172
|
+
skipped: boolean;
|
|
173
|
+
}>;
|
|
174
|
+
}
|
|
175
|
+
export declare function createContinuationAttemptTracker(): ContinuationAttemptTracker;
|
|
176
|
+
/**
|
|
177
|
+
* Build a continuation-advancement prompt when the normal todo-sourced
|
|
178
|
+
* prompt has stalled (no board progress). The text varies by attempt count
|
|
179
|
+
* to avoid triggering the repetition guard.
|
|
180
|
+
*
|
|
181
|
+
* @param stalledTodo The todo that didn't progress (used for its id/label).
|
|
182
|
+
* @param nextTodo The next open todo to advance to, or null if none left.
|
|
183
|
+
* @param attemptCount How many times stalledTodo has been retried.
|
|
184
|
+
* @param todos The full current todo list for board context.
|
|
185
|
+
* @returns A varied advancement prompt for the next auto-feed.
|
|
186
|
+
*/
|
|
187
|
+
export declare function generateAdvancementPrompt(stalledTodo: {
|
|
188
|
+
id: string;
|
|
189
|
+
content: string;
|
|
190
|
+
}, nextTodo: {
|
|
191
|
+
id: string;
|
|
192
|
+
content: string;
|
|
193
|
+
status: string;
|
|
194
|
+
activeForm?: string | undefined;
|
|
195
|
+
} | null, attemptCount: number, todos: readonly {
|
|
196
|
+
id: string;
|
|
197
|
+
content: string;
|
|
198
|
+
status: string;
|
|
199
|
+
activeForm?: string | undefined;
|
|
200
|
+
}[]): string;
|
|
201
|
+
export interface StalledTodoMatch {
|
|
202
|
+
id: string;
|
|
203
|
+
content: string;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Identify the todo whose content appears in a stalled continuation prompt.
|
|
207
|
+
* Scans the board for the in_progress item first (that's what
|
|
208
|
+
* `resolveContinuation` picks), then falls back to any pending item whose
|
|
209
|
+
* text appears in the prompt. Last resort: the first non-completed todo.
|
|
210
|
+
*
|
|
211
|
+
* Returns `null` when the board is empty or all items are completed.
|
|
212
|
+
*/
|
|
213
|
+
export declare function matchTodoIdFromPrompt(todos: readonly {
|
|
214
|
+
id: string;
|
|
215
|
+
content: string;
|
|
216
|
+
status: string;
|
|
217
|
+
activeForm?: string | undefined;
|
|
218
|
+
}[], prompt: string): StalledTodoMatch | null;
|
|
219
|
+
/**
|
|
220
|
+
* Maximum total advancement attempts before we give up on the board and
|
|
221
|
+
* halt. Otherwise a board where no agent turn makes progress cycles through
|
|
222
|
+
* the same todos indefinitely. Reset on manual user input.
|
|
223
|
+
*/
|
|
224
|
+
export declare const MAX_ADVANCEMENT_ATTEMPTS = 9;
|
|
158
225
|
export declare function createAutoProceedLoopGuard(options?: LoopGuardOptions): AutoProceedLoopGuard;
|
|
159
226
|
//# sourceMappingURL=auto-proceed-loop-guard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-proceed-loop-guard.d.ts","sourceRoot":"","sources":["../src/auto-proceed-loop-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,0BAA0B,QAO3B,CAAC;AAEb,MAAM,MAAM,wBAAwB,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAEjE,MAAM,WAAW,wBAAyB,SAAQ,gBAAgB;IAChE;;;;;;;OAOG;IACH,MAAM,EAAE,wBAAwB,CAAC;CAClC;AAED,MAAM,WAAW,oBAAoB;IACnC;;;;;OAKG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAAC;IACzC;;;;;;;;OAQG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,wBAAwB,CAAC;IACzD;;;OAGG;IACH,KAAK,IAAI,IAAI,CAAC;IACd;;;;OAIG;IACH,OAAO,IAAI,SAAS,MAAM,EAAE,CAAC;IAC7B;;OAEG;IACH,IAAI,IAAI,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,gBAAqB,GAAG,oBAAoB,CAqE/F"}
|
|
1
|
+
{"version":3,"file":"auto-proceed-loop-guard.d.ts","sourceRoot":"","sources":["../src/auto-proceed-loop-guard.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,6 +10,97 @@ var GROUNDED_NO_PROGRESS_STEER = [
|
|
|
10
10
|
"- remove or reword items that no longer apply.",
|
|
11
11
|
"Then continue the work. If you are blocked, state exactly what is blocking you instead of repeating the same turn."
|
|
12
12
|
].join("\n");
|
|
13
|
+
function createContinuationAttemptTracker() {
|
|
14
|
+
const attempts = /* @__PURE__ */ new Map();
|
|
15
|
+
const skipped = /* @__PURE__ */ new Set();
|
|
16
|
+
return {
|
|
17
|
+
recordAttempt(todoId) {
|
|
18
|
+
const count = (attempts.get(todoId) ?? 0) + 1;
|
|
19
|
+
attempts.set(todoId, count);
|
|
20
|
+
return count;
|
|
21
|
+
},
|
|
22
|
+
getAttempts(todoId) {
|
|
23
|
+
return attempts.get(todoId) ?? 0;
|
|
24
|
+
},
|
|
25
|
+
markSkipped(todoId) {
|
|
26
|
+
skipped.add(todoId);
|
|
27
|
+
},
|
|
28
|
+
isSkipped(todoId) {
|
|
29
|
+
return skipped.has(todoId);
|
|
30
|
+
},
|
|
31
|
+
reset() {
|
|
32
|
+
attempts.clear();
|
|
33
|
+
skipped.clear();
|
|
34
|
+
},
|
|
35
|
+
snapshot() {
|
|
36
|
+
const result = {};
|
|
37
|
+
const allIds = /* @__PURE__ */ new Set([...attempts.keys(), ...skipped]);
|
|
38
|
+
for (const id of allIds) {
|
|
39
|
+
result[id] = { attempts: attempts.get(id) ?? 0, skipped: skipped.has(id) };
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
var ADVANCE_PREAMBLES = [
|
|
46
|
+
"Pivoting to the next open item \u2014 previous todo still pending after {attempts} attempt(s).",
|
|
47
|
+
"Shifting focus to a different task while the previous item ({label}) remains open after {attempts} attempt(s).",
|
|
48
|
+
"No board progress on the current item after {attempts} attempt(s). Continuing with the next todo.",
|
|
49
|
+
"The previous item is still unresolved after {attempts} try(ies). Moving on down the list.",
|
|
50
|
+
"Advancing to the next piece of work after {attempts} unproductive attempt(s) on the prior one.",
|
|
51
|
+
"Taking a different angle \u2014 the stalled item ({label}) stays open after {attempts} attempt(s). Pressing ahead.",
|
|
52
|
+
"The todo board shows no movement on the current task after {attempts} attempt(s). Switching gears.",
|
|
53
|
+
"No change on the board from the last {attempts} continuation(s). Proceeding to the next open item."
|
|
54
|
+
];
|
|
55
|
+
function generateAdvancementPrompt(stalledTodo, nextTodo, attemptCount, todos) {
|
|
56
|
+
const idx = Math.max(0, Math.min(attemptCount - 1, ADVANCE_PREAMBLES.length - 1));
|
|
57
|
+
const preamble = ADVANCE_PREAMBLES[idx].replace(/\{attempts\}/g, () => String(attemptCount)).replace(/\{label\}/g, () => stalledTodo.content.slice(0, 72));
|
|
58
|
+
if (!nextTodo) {
|
|
59
|
+
return [
|
|
60
|
+
preamble,
|
|
61
|
+
"",
|
|
62
|
+
"No more open items remain on the todo list. What has been accomplished and what should I do next?"
|
|
63
|
+
].join("\n");
|
|
64
|
+
}
|
|
65
|
+
const itemLabel = nextTodo.status === "in_progress" && nextTodo.activeForm ? nextTodo.activeForm : nextTodo.content;
|
|
66
|
+
const done = todos.filter((t) => t.status === "completed").length;
|
|
67
|
+
const total = todos.length;
|
|
68
|
+
const lines = [preamble, "", `Proceed with: ${itemLabel}`];
|
|
69
|
+
if (total > 0) {
|
|
70
|
+
lines.push("", `Todo board (${done}/${total} done):`);
|
|
71
|
+
lines.push(
|
|
72
|
+
...todos.slice(0, 20).map((t) => {
|
|
73
|
+
const mark = t.status === "completed" ? "[x]" : t.status === "in_progress" ? "[~]" : "[ ]";
|
|
74
|
+
const flat = t.content.replace(/\n|\r/g, " ");
|
|
75
|
+
return ` ${mark} ${flat}`;
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
lines.push("", "If this item is already done, mark it complete and move to the next open todo. Keep the board honest as you work.");
|
|
80
|
+
return lines.join("\n");
|
|
81
|
+
}
|
|
82
|
+
function matchTodoIdFromPrompt(todos, prompt) {
|
|
83
|
+
if (todos.length === 0 || !prompt) return null;
|
|
84
|
+
const lowerPrompt = prompt.toLowerCase();
|
|
85
|
+
const inProgress = todos.find((t) => t.status === "in_progress");
|
|
86
|
+
if (inProgress) {
|
|
87
|
+
const label = (inProgress.activeForm ?? inProgress.content).toLowerCase();
|
|
88
|
+
if (label.length > 5 && lowerPrompt.includes(label)) {
|
|
89
|
+
return { id: inProgress.id, content: inProgress.activeForm ?? inProgress.content };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (const t of todos) {
|
|
93
|
+
if (t.status === "completed") continue;
|
|
94
|
+
if (inProgress && t.id === inProgress.id) continue;
|
|
95
|
+
const label = (t.activeForm ?? t.content).toLowerCase();
|
|
96
|
+
if (label.length > 5 && lowerPrompt.includes(label)) {
|
|
97
|
+
return { id: t.id, content: t.activeForm ?? t.content };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const firstOpen = todos.find((t) => t.status !== "completed");
|
|
101
|
+
return firstOpen ? { id: firstOpen.id, content: firstOpen.activeForm ?? firstOpen.content } : null;
|
|
102
|
+
}
|
|
103
|
+
var MAX_ADVANCEMENT_ATTEMPTS = 9;
|
|
13
104
|
function createAutoProceedLoopGuard(options = {}) {
|
|
14
105
|
const safeIntegerOption = (value, fallback) => {
|
|
15
106
|
if (value === void 0) return fallback;
|
|
@@ -63,7 +154,11 @@ function createAutoProceedLoopGuard(options = {}) {
|
|
|
63
154
|
}
|
|
64
155
|
export {
|
|
65
156
|
GROUNDED_NO_PROGRESS_STEER,
|
|
157
|
+
MAX_ADVANCEMENT_ATTEMPTS,
|
|
66
158
|
createAutoProceedLoopGuard,
|
|
159
|
+
createContinuationAttemptTracker,
|
|
160
|
+
generateAdvancementPrompt,
|
|
161
|
+
matchTodoIdFromPrompt,
|
|
67
162
|
normalizeForRepetition
|
|
68
163
|
};
|
|
69
164
|
//# sourceMappingURL=auto-proceed-loop-guard.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/auto-proceed-loop-guard.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Auto-proceed loop guard.\n *\n * ## Problem\n *\n * The REPL and TUI both implement an \"auto\" autonomy mode that, after every\n * completed agent turn, feeds the next suggestion (or top auto=\"true\" item)\n * back as a fresh prompt. When the agent emits the same `<nextsteps>` block\n * on every reply \u2014 which happens whenever the model's output is stable but\n * autonomy treats the next step as \"still actionable\" \u2014 the loop self-feeds\n * the same instruction 2\u20133 times in a row before the longer\n * `autoProceedMaxIterations` cap (default 50) trips.\n *\n * The user reports the visible symptom: the same response, including the same\n * `<nextsteps>` block, repeats. Manual input from the user is the only thing\n * that breaks out. They suspect the next-steps mechanism is the source.\n *\n * The 50-iteration cap is intentionally loose \u2014 it is a runaway safety net,\n * not a UX signal. By the time it trips, the user has already watched the\n * same response 50 times.\n *\n * ## Approach\n *\n * A small, pure, browser-safe stateful helper that records the last few\n * prompts fed through the auto-proceed / auto-submit path. It normalizes\n * prompts (whitespace, casing) so trivial rewordings are not collapsed, but\n * identical re-feeds are. When the same normalized prompt is fed\n * `repeatThreshold` (default 2) times in a row, the guard returns\n * `{ shouldHalt: true }`.\n *\n * Callers are expected to:\n * 1. Call `record(prompt)` immediately before each auto-feed.\n * 2. If `shouldHalt` is true, clear the suggestion + auto-suggestion store,\n * cancel any pending countdown, and surface a \"we detected a loop\"\n * message asking the user what's happening. Do NOT feed the prompt.\n * 3. Call `reset()` whenever the user types anything manually (REPL manual\n * input or any user-driven submit) so the next auto-feed starts clean.\n *\n * The default `repeatThreshold` is 2 \u2014 i.e. the second consecutive identical\n * feed (the first re-feed of an already-seen prompt) halts the loop. That\n * matches the user's report (\"receiving the same prompt 2-3 times in a row\n * causes the system to enter a loop\"). Setting it to 2 catches the loop on\n * the first identical re-feed rather than after the 50-iteration runaway cap trips.\n *\n * The window size is intentionally small (default 3). The loop we care about\n * is the immediate repetition \u2014 a longer history would misfire on legitimate\n * \"do X, then do Y, then do X\" sequences the model occasionally returns to.\n *\n * This module is BROWSER-SAFE \u2014 no Node-only imports \u2014 so it can be imported\n * from Vite-bundled WebUI as well as Node-based CLI/TUI.\n */\n\n/**\n * Normalize a prompt for repetition comparison. Whitespace is collapsed and\n * trimmed; case is folded to lowercase. Nothing semantic is removed, so two\n * prompts that differ in any meaningful way (extra word, different code,\n * different file path) compare as distinct. Two prompts that differ only by\n * leading/trailing whitespace and casing are treated as identical.\n */\nexport function normalizeForRepetition(prompt: string): string {\n return prompt.replace(/\\s+/g, ' ').trim().toLowerCase();\n}\n\nexport interface LoopGuardOptions {\n /**\n * How many consecutive identical prompts (after normalization) trigger a\n * halt. Default 2 \u2014 i.e. the second consecutive identical feed (the first\n * re-feed of an already-seen prompt) halts the loop. Clamped to >= 2.\n */\n repeatThreshold?: number;\n /**\n * How many of the most recent feeds to retain for comparison. The window\n * is searched from newest to oldest; the guard cares about the run of\n * identical entries ending at the most recent one, not about identical\n * entries anywhere in history. Default 3.\n */\n windowSize?: number;\n}\n\nexport interface RepetitionSignal {\n /** The normalized prompt that just got recorded. */\n normalized: string;\n /**\n * Number of consecutive identical feeds ending at the most recent feed,\n * including this one. Always >= 1.\n */\n runLength: number;\n /**\n * True when the run length has crossed `repeatThreshold`. Callers must\n * stop feeding and surface a user prompt instead.\n */\n shouldHalt: boolean;\n}\n\n/**\n * Steer text appended (once) to a grounded todo continuation when the board\n * has not moved between two consecutive automatic turns. Grounded prompts are\n * synthesized from durable todo state, so an identical re-feed means the whole\n * previous turn produced zero board movement \u2014 usually the model worked but\n * forgot to update the board. One explicit steer fixes that far more often\n * than halting; the halt only fires if the board stays frozen AFTER the steer.\n */\nexport const GROUNDED_NO_PROGRESS_STEER = [\n 'NOTE: The todo board has not changed since the previous automatic continuation \u2014 this exact instruction is being repeated.',\n 'Before anything else, bring the board up to date so the loop stays grounded:',\n '- mark work that is actually finished as completed,',\n '- split the current item into smaller concrete sub-items if it needs more than one turn,',\n '- remove or reword items that no longer apply.',\n 'Then continue the work. If you are blocked, state exactly what is blocking you instead of repeating the same turn.',\n].join('\\n');\n\nexport type GroundedRepetitionAction = 'feed' | 'steer' | 'halt';\n\nexport interface GroundedRepetitionSignal extends RepetitionSignal {\n /**\n * What the caller should do with this grounded (todo-sourced) prompt:\n * - `feed` \u2014 no repetition; feed the prompt as-is.\n * - `steer` \u2014 first repetition; feed the prompt with\n * {@link GROUNDED_NO_PROGRESS_STEER} appended instead of halting.\n * - `halt` \u2014 the board stayed frozen even after a steer; break the loop\n * exactly like a `shouldHalt` from {@link AutoProceedLoopGuard.record}.\n */\n action: GroundedRepetitionAction;\n}\n\nexport interface AutoProceedLoopGuard {\n /**\n * Record a prompt that is about to be auto-fed. Returns the repetition\n * signal for that prompt. When `shouldHalt` is true the caller MUST NOT\n * feed the prompt \u2014 it has been recorded for post-mortem inspection, but\n * the loop must be broken instead.\n */\n record(prompt: string): RepetitionSignal;\n /**\n * Record a GROUNDED prompt \u2014 one synthesized from durable state (the todo\n * board) rather than echoed from model output. Repetition of a grounded\n * prompt is a \"no board progress\" signal, not necessarily an echo loop, so\n * the first repetition asks the caller to steer (append\n * {@link GROUNDED_NO_PROGRESS_STEER} to the fed text) and only a repetition\n * that survives the steer halts. The steer state resets whenever a\n * different prompt is recorded (board moved) or on {@link reset}.\n */\n recordGrounded(prompt: string): GroundedRepetitionSignal;\n /**\n * Drop the history. Call this on any manual user input so a fresh run\n * starts with no memory of the prior cycle.\n */\n reset(): void;\n /**\n * Read-only view of the most recent normalized prompts (newest last).\n * Useful for diagnostics and for the message shown to the user when the\n * guard halts the loop.\n */\n history(): readonly string[];\n /**\n * How many entries the guard is currently retaining.\n */\n size(): number;\n}\n\n/**\n * Build a fresh loop guard. Defaults: `repeatThreshold = 2`, `windowSize = 3`.\n *\n * @example\n * const guard = createAutoProceedLoopGuard();\n * for (const prompt of candidates) {\n * const signal = guard.record(prompt);\n * if (signal.shouldHalt) {\n * haltAutoProceedAndAskUser(signal);\n * break;\n * }\n * feedPrompt(prompt);\n * }\n */\nexport function createAutoProceedLoopGuard(options: LoopGuardOptions = {}): AutoProceedLoopGuard {\n // Validate options defensively. Non-finite values (NaN, Infinity) MUST\n // fall back to the documented default \u2014 a guard that silently disables\n // itself is worse than no guard at all. Specifically:\n // - `repeatThreshold: NaN` would cause `Math.floor(NaN) = NaN`,\n // `Math.max(2, NaN) = NaN`, and `runLength >= NaN` is always false,\n // making the guard permanent no-op.\n // - `windowSize: NaN` / `Infinity` would skip the trim branch entirely\n // (`buffer.length > NaN` and `buffer.length > Infinity` are both\n // false), so the buffer would grow without bound.\n const safeIntegerOption = (value: number | undefined, fallback: number): number => {\n if (value === undefined) return fallback;\n if (!Number.isFinite(value)) return fallback;\n return Math.floor(value);\n };\n const repeatThreshold = Math.max(2, safeIntegerOption(options.repeatThreshold, 2));\n const windowSize = Math.max(repeatThreshold, safeIntegerOption(options.windowSize, 3));\n let buffer: string[] = [];\n // Normalized grounded prompt we already steered for; a halt only fires for\n // a grounded prompt that repeats again AFTER its steer.\n let steeredFor: string | null = null;\n\n function record(prompt: string): RepetitionSignal {\n const normalized = normalizeForRepetition(prompt);\n buffer.push(normalized);\n if (buffer.length > windowSize) {\n buffer = buffer.slice(buffer.length - windowSize);\n }\n // Count the trailing run of identical normalized prompts.\n let runLength = 0;\n for (let i = buffer.length - 1; i >= 0; i--) {\n if (buffer[i] === normalized) runLength++;\n else break;\n }\n return {\n normalized,\n runLength,\n shouldHalt: runLength >= repeatThreshold,\n };\n }\n\n function recordGrounded(prompt: string): GroundedRepetitionSignal {\n const signal = record(prompt);\n if (!signal.shouldHalt) {\n // The board moved (different prompt) \u2014 clear any pending steer state.\n steeredFor = null;\n return { ...signal, shouldHalt: false, action: 'feed' };\n }\n if (steeredFor === signal.normalized) {\n return { ...signal, action: 'halt' };\n }\n steeredFor = signal.normalized;\n return { ...signal, shouldHalt: false, action: 'steer' };\n }\n\n function reset(): void {\n buffer = [];\n steeredFor = null;\n }\n\n function history(): readonly string[] {\n return buffer.slice();\n }\n\n function size(): number {\n return buffer.length;\n }\n\n return { record, recordGrounded, reset, history, size };\n}\n"],
|
|
5
|
-
"mappings": ";AA2DO,SAAS,uBAAuB,QAAwB;AAC7D,SAAO,OAAO,QAAQ,QAAQ,GAAG,EAAE,KAAK,EAAE,YAAY;AACxD;AAyCO,IAAM,6BAA6B;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,IAAI;
|
|
4
|
+
"sourcesContent": ["/**\n * Auto-proceed loop guard.\n *\n * ## Problem\n *\n * The REPL and TUI both implement an \"auto\" autonomy mode that, after every\n * completed agent turn, feeds the next suggestion (or top auto=\"true\" item)\n * back as a fresh prompt. When the agent emits the same `<nextsteps>` block\n * on every reply \u2014 which happens whenever the model's output is stable but\n * autonomy treats the next step as \"still actionable\" \u2014 the loop self-feeds\n * the same instruction 2\u20133 times in a row before the longer\n * `autoProceedMaxIterations` cap (default 50) trips.\n *\n * The user reports the visible symptom: the same response, including the same\n * `<nextsteps>` block, repeats. Manual input from the user is the only thing\n * that breaks out. They suspect the next-steps mechanism is the source.\n *\n * The 50-iteration cap is intentionally loose \u2014 it is a runaway safety net,\n * not a UX signal. By the time it trips, the user has already watched the\n * same response 50 times.\n *\n * ## Approach\n *\n * A small, pure, browser-safe stateful helper that records the last few\n * prompts fed through the auto-proceed / auto-submit path. It normalizes\n * prompts (whitespace, casing) so trivial rewordings are not collapsed, but\n * identical re-feeds are. When the same normalized prompt is fed\n * `repeatThreshold` (default 2) times in a row, the guard returns\n * `{ shouldHalt: true }`.\n *\n * Callers are expected to:\n * 1. Call `record(prompt)` immediately before each auto-feed.\n * 2. If `shouldHalt` is true, clear the suggestion + auto-suggestion store,\n * cancel any pending countdown, and surface a \"we detected a loop\"\n * message asking the user what's happening. Do NOT feed the prompt.\n * 3. Call `reset()` whenever the user types anything manually (REPL manual\n * input or any user-driven submit) so the next auto-feed starts clean.\n *\n * The default `repeatThreshold` is 2 \u2014 i.e. the second consecutive identical\n * feed (the first re-feed of an already-seen prompt) halts the loop. That\n * matches the user's report (\"receiving the same prompt 2-3 times in a row\n * causes the system to enter a loop\"). Setting it to 2 catches the loop on\n * the first identical re-feed rather than after the 50-iteration runaway cap trips.\n *\n * The window size is intentionally small (default 3). The loop we care about\n * is the immediate repetition \u2014 a longer history would misfire on legitimate\n * \"do X, then do Y, then do X\" sequences the model occasionally returns to.\n *\n * This module is BROWSER-SAFE \u2014 no Node-only imports \u2014 so it can be imported\n * from Vite-bundled WebUI as well as Node-based CLI/TUI.\n */\n\n/**\n * Normalize a prompt for repetition comparison. Whitespace is collapsed and\n * trimmed; case is folded to lowercase. Nothing semantic is removed, so two\n * prompts that differ in any meaningful way (extra word, different code,\n * different file path) compare as distinct. Two prompts that differ only by\n * leading/trailing whitespace and casing are treated as identical.\n */\nexport function normalizeForRepetition(prompt: string): string {\n return prompt.replace(/\\s+/g, ' ').trim().toLowerCase();\n}\n\nexport interface LoopGuardOptions {\n /**\n * How many consecutive identical prompts (after normalization) trigger a\n * halt. Default 2 \u2014 i.e. the second consecutive identical feed (the first\n * re-feed of an already-seen prompt) halts the loop. Clamped to >= 2.\n */\n repeatThreshold?: number;\n /**\n * How many of the most recent feeds to retain for comparison. The window\n * is searched from newest to oldest; the guard cares about the run of\n * identical entries ending at the most recent one, not about identical\n * entries anywhere in history. Default 3.\n */\n windowSize?: number;\n}\n\nexport interface RepetitionSignal {\n /** The normalized prompt that just got recorded. */\n normalized: string;\n /**\n * Number of consecutive identical feeds ending at the most recent feed,\n * including this one. Always >= 1.\n */\n runLength: number;\n /**\n * True when the run length has crossed `repeatThreshold`. Callers must\n * stop feeding and surface a user prompt instead.\n */\n shouldHalt: boolean;\n}\n\n/**\n * Steer text appended (once) to a grounded todo continuation when the board\n * has not moved between two consecutive automatic turns. Grounded prompts are\n * synthesized from durable todo state, so an identical re-feed means the whole\n * previous turn produced zero board movement \u2014 usually the model worked but\n * forgot to update the board. One explicit steer fixes that far more often\n * than halting; the halt only fires if the board stays frozen AFTER the steer.\n */\nexport const GROUNDED_NO_PROGRESS_STEER = [\n 'NOTE: The todo board has not changed since the previous automatic continuation \u2014 this exact instruction is being repeated.',\n 'Before anything else, bring the board up to date so the loop stays grounded:',\n '- mark work that is actually finished as completed,',\n '- split the current item into smaller concrete sub-items if it needs more than one turn,',\n '- remove or reword items that no longer apply.',\n 'Then continue the work. If you are blocked, state exactly what is blocking you instead of repeating the same turn.',\n].join('\\n');\n\nexport type GroundedRepetitionAction = 'feed' | 'steer' | 'halt';\n\nexport interface GroundedRepetitionSignal extends RepetitionSignal {\n /**\n * What the caller should do with this grounded (todo-sourced) prompt:\n * - `feed` \u2014 no repetition; feed the prompt as-is.\n * - `steer` \u2014 first repetition; feed the prompt with\n * {@link GROUNDED_NO_PROGRESS_STEER} appended instead of halting.\n * - `halt` \u2014 the board stayed frozen even after a steer; break the loop\n * exactly like a `shouldHalt` from {@link AutoProceedLoopGuard.record}.\n */\n action: GroundedRepetitionAction;\n}\n\nexport interface AutoProceedLoopGuard {\n /**\n * Record a prompt that is about to be auto-fed. Returns the repetition\n * signal for that prompt. When `shouldHalt` is true the caller MUST NOT\n * feed the prompt \u2014 it has been recorded for post-mortem inspection, but\n * the loop must be broken instead.\n */\n record(prompt: string): RepetitionSignal;\n /**\n * Record a GROUNDED prompt \u2014 one synthesized from durable state (the todo\n * board) rather than echoed from model output. Repetition of a grounded\n * prompt is a \"no board progress\" signal, not necessarily an echo loop, so\n * the first repetition asks the caller to steer (append\n * {@link GROUNDED_NO_PROGRESS_STEER} to the fed text) and only a repetition\n * that survives the steer halts. The steer state resets whenever a\n * different prompt is recorded (board moved) or on {@link reset}.\n */\n recordGrounded(prompt: string): GroundedRepetitionSignal;\n /**\n * Drop the history. Call this on any manual user input so a fresh run\n * starts with no memory of the prior cycle.\n */\n reset(): void;\n /**\n * Read-only view of the most recent normalized prompts (newest last).\n * Useful for diagnostics and for the message shown to the user when the\n * guard halts the loop.\n */\n history(): readonly string[];\n /**\n * How many entries the guard is currently retaining.\n */\n size(): number;\n}\n\n/**\n * Build a fresh loop guard. Defaults: `repeatThreshold = 2`, `windowSize = 3`.\n *\n * @example\n * const guard = createAutoProceedLoopGuard();\n * for (const prompt of candidates) {\n * const signal = guard.record(prompt);\n * if (signal.shouldHalt) {\n * haltAutoProceedAndAskUser(signal);\n * break;\n * }\n * feedPrompt(prompt);\n * }\n */\n// \u2500\u2500 Continuation-attempt tracker \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Tracks per-todo how many times auto-submit has continued on it, so\n// advancement prompts can vary wording and report \"attempt N\" rather than\n// feeding an identical instruction. Resets on manual user input.\n\nexport interface ContinuationAttemptTracker {\n /** Record an attempt against a todo. Returns the new attempt count (\u2265 1). */\n recordAttempt(todoId: string): number;\n /** How many times continuation has been attempted for this todo (0 = never). */\n getAttempts(todoId: string): number;\n /** Mark a todo as \"skipped\" \u2014 a later attempt tried advancing past it. */\n markSkipped(todoId: string): void;\n /** True when this todo has been skipped due to no board progress. */\n isSkipped(todoId: string): boolean;\n /** Reset every tracked todo (call on manual user input). */\n reset(): void;\n /** Read-only snapshot for diagnostic use. */\n snapshot(): Record<string, { attempts: number; skipped: boolean }>;\n}\n\nexport function createContinuationAttemptTracker(): ContinuationAttemptTracker {\n const attempts = new Map<string, number>();\n const skipped = new Set<string>();\n\n return {\n recordAttempt(todoId: string): number {\n const count = (attempts.get(todoId) ?? 0) + 1;\n attempts.set(todoId, count);\n return count;\n },\n getAttempts(todoId: string): number {\n return attempts.get(todoId) ?? 0;\n },\n markSkipped(todoId: string): void {\n skipped.add(todoId);\n },\n isSkipped(todoId: string): boolean {\n return skipped.has(todoId);\n },\n reset(): void {\n attempts.clear();\n skipped.clear();\n },\n snapshot(): Record<string, { attempts: number; skipped: boolean }> {\n const result: Record<string, { attempts: number; skipped: boolean }> = {};\n const allIds = new Set([...attempts.keys(), ...skipped]);\n for (const id of allIds) {\n result[id] = { attempts: attempts.get(id) ?? 0, skipped: skipped.has(id) };\n }\n return result;\n },\n };\n}\n\n// \u2500\u2500 Advancement prompt generator \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// When a grounded (todo-sourced) prompt stalls because the board hasn't\n// moved, instead of halting we advance to the next open todo and generate\n// a varied prompt that references the stalled item's attempt count. The\n// wording rotates through a pool so identical todos produce different\n// text across attempts.\n\nconst ADVANCE_PREAMBLES = [\n 'Pivoting to the next open item \u2014 previous todo still pending after {attempts} attempt(s).',\n 'Shifting focus to a different task while the previous item ({label}) remains open after {attempts} attempt(s).',\n 'No board progress on the current item after {attempts} attempt(s). Continuing with the next todo.',\n 'The previous item is still unresolved after {attempts} try(ies). Moving on down the list.',\n 'Advancing to the next piece of work after {attempts} unproductive attempt(s) on the prior one.',\n 'Taking a different angle \u2014 the stalled item ({label}) stays open after {attempts} attempt(s). Pressing ahead.',\n 'The todo board shows no movement on the current task after {attempts} attempt(s). Switching gears.',\n 'No change on the board from the last {attempts} continuation(s). Proceeding to the next open item.',\n];\n\n/**\n * Build a continuation-advancement prompt when the normal todo-sourced\n * prompt has stalled (no board progress). The text varies by attempt count\n * to avoid triggering the repetition guard.\n *\n * @param stalledTodo The todo that didn't progress (used for its id/label).\n * @param nextTodo The next open todo to advance to, or null if none left.\n * @param attemptCount How many times stalledTodo has been retried.\n * @param todos The full current todo list for board context.\n * @returns A varied advancement prompt for the next auto-feed.\n */\nexport function generateAdvancementPrompt(\n stalledTodo: { id: string; content: string },\n nextTodo: { id: string; content: string; status: string; activeForm?: string | undefined } | null,\n attemptCount: number,\n todos: readonly { id: string; content: string; status: string; activeForm?: string | undefined }[],\n): string {\n const idx = Math.max(0, Math.min(attemptCount - 1, ADVANCE_PREAMBLES.length - 1));\n const preamble = ADVANCE_PREAMBLES[idx]!\n .replace(/\\{attempts\\}/g, () => String(attemptCount))\n .replace(/\\{label\\}/g, () => stalledTodo.content.slice(0, 72));\n\n if (!nextTodo) {\n return [\n preamble,\n '',\n 'No more open items remain on the todo list. What has been accomplished and what should I do next?',\n ].join('\\n');\n }\n\n const itemLabel =\n nextTodo.status === 'in_progress' && nextTodo.activeForm\n ? nextTodo.activeForm\n : nextTodo.content;\n\n const done = todos.filter((t) => t.status === 'completed').length;\n const total = todos.length;\n\n const lines: string[] = [preamble, '', `Proceed with: ${itemLabel}`];\n\n if (total > 0) {\n lines.push('', `Todo board (${done}/${total} done):`);\n lines.push(\n ...todos.slice(0, 20).map((t) => {\n const mark = t.status === 'completed' ? '[x]' : t.status === 'in_progress' ? '[~]' : '[ ]';\n const flat = t.content.replace(/\\n|\\r/g, ' ');\n return ` ${mark} ${flat}`;\n }),\n );\n }\n\n lines.push('', 'If this item is already done, mark it complete and move to the next open todo. Keep the board honest as you work.');\n\n return lines.join('\\n');\n}\n\n// \u2500\u2500 Stalled-todo matching \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Shared helper used by both TUI and WebUI to identify which todo produced a\n// stalled continuation prompt. Matches by content against the in_progress /\n// pending todos in the board before falling back to the first open item.\n\nexport interface StalledTodoMatch {\n id: string;\n content: string;\n}\n\n/**\n * Identify the todo whose content appears in a stalled continuation prompt.\n * Scans the board for the in_progress item first (that's what\n * `resolveContinuation` picks), then falls back to any pending item whose\n * text appears in the prompt. Last resort: the first non-completed todo.\n *\n * Returns `null` when the board is empty or all items are completed.\n */\nexport function matchTodoIdFromPrompt(\n todos: readonly { id: string; content: string; status: string; activeForm?: string | undefined }[],\n prompt: string,\n): StalledTodoMatch | null {\n if (todos.length === 0 || !prompt) return null;\n const lowerPrompt = prompt.toLowerCase();\n\n // 1. In-progress item \u2014 resolveContinuation promotes this first.\n const inProgress = todos.find((t) => t.status === 'in_progress');\n if (inProgress) {\n const label = (inProgress.activeForm ?? inProgress.content).toLowerCase();\n if (label.length > 5 && lowerPrompt.includes(label)) {\n return { id: inProgress.id, content: inProgress.activeForm ?? inProgress.content };\n }\n }\n\n // 2. Any pending item whose content appears in the prompt (board snapshot).\n for (const t of todos) {\n if (t.status === 'completed') continue;\n if (inProgress && t.id === inProgress.id) continue;\n const label = (t.activeForm ?? t.content).toLowerCase();\n if (label.length > 5 && lowerPrompt.includes(label)) {\n return { id: t.id, content: t.activeForm ?? t.content };\n }\n }\n\n // 3. Last resort \u2014 pick the first non-completed todo.\n const firstOpen = todos.find((t) => t.status !== 'completed');\n return firstOpen ? { id: firstOpen.id, content: firstOpen.activeForm ?? firstOpen.content } : null;\n}\n\n/**\n * Maximum total advancement attempts before we give up on the board and\n * halt. Otherwise a board where no agent turn makes progress cycles through\n * the same todos indefinitely. Reset on manual user input.\n */\nexport const MAX_ADVANCEMENT_ATTEMPTS = 9;\n\nexport function createAutoProceedLoopGuard(options: LoopGuardOptions = {}): AutoProceedLoopGuard {\n // Validate options defensively. Non-finite values (NaN, Infinity) MUST\n // fall back to the documented default \u2014 a guard that silently disables\n // itself is worse than no guard at all. Specifically:\n // - `repeatThreshold: NaN` would cause `Math.floor(NaN) = NaN`,\n // `Math.max(2, NaN) = NaN`, and `runLength >= NaN` is always false,\n // making the guard permanent no-op.\n // - `windowSize: NaN` / `Infinity` would skip the trim branch entirely\n // (`buffer.length > NaN` and `buffer.length > Infinity` are both\n // false), so the buffer would grow without bound.\n const safeIntegerOption = (value: number | undefined, fallback: number): number => {\n if (value === undefined) return fallback;\n if (!Number.isFinite(value)) return fallback;\n return Math.floor(value);\n };\n const repeatThreshold = Math.max(2, safeIntegerOption(options.repeatThreshold, 2));\n const windowSize = Math.max(repeatThreshold, safeIntegerOption(options.windowSize, 3));\n let buffer: string[] = [];\n // Normalized grounded prompt we already steered for; a halt only fires for\n // a grounded prompt that repeats again AFTER its steer.\n let steeredFor: string | null = null;\n\n function record(prompt: string): RepetitionSignal {\n const normalized = normalizeForRepetition(prompt);\n buffer.push(normalized);\n if (buffer.length > windowSize) {\n buffer = buffer.slice(buffer.length - windowSize);\n }\n // Count the trailing run of identical normalized prompts.\n let runLength = 0;\n for (let i = buffer.length - 1; i >= 0; i--) {\n if (buffer[i] === normalized) runLength++;\n else break;\n }\n return {\n normalized,\n runLength,\n shouldHalt: runLength >= repeatThreshold,\n };\n }\n\n function recordGrounded(prompt: string): GroundedRepetitionSignal {\n const signal = record(prompt);\n if (!signal.shouldHalt) {\n // The board moved (different prompt) \u2014 clear any pending steer state.\n steeredFor = null;\n return { ...signal, shouldHalt: false, action: 'feed' };\n }\n if (steeredFor === signal.normalized) {\n return { ...signal, action: 'halt' };\n }\n steeredFor = signal.normalized;\n return { ...signal, shouldHalt: false, action: 'steer' };\n }\n\n function reset(): void {\n buffer = [];\n steeredFor = null;\n }\n\n function history(): readonly string[] {\n return buffer.slice();\n }\n\n function size(): number {\n return buffer.length;\n }\n\n return { record, recordGrounded, reset, history, size };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/bash.js
CHANGED
|
@@ -935,7 +935,11 @@ var PersistentProcessRegistry = class {
|
|
|
935
935
|
cleanupRunning = false;
|
|
936
936
|
isShuttingDown = false;
|
|
937
937
|
onProcessExit = () => {
|
|
938
|
-
|
|
938
|
+
this.runInBackground(
|
|
939
|
+
this.syncToPersistent(),
|
|
940
|
+
"process_registry.exit_sync_failed",
|
|
941
|
+
"PersistentProcessRegistry: exit sync failed"
|
|
942
|
+
);
|
|
939
943
|
};
|
|
940
944
|
constructor(baseRegistry) {
|
|
941
945
|
this.instanceId = generateInstanceId();
|
|
@@ -955,6 +959,11 @@ var PersistentProcessRegistry = class {
|
|
|
955
959
|
if (!isNodeError(err) || err.code !== "EEXIST") throw err;
|
|
956
960
|
}
|
|
957
961
|
}
|
|
962
|
+
runInBackground(operation, event, message) {
|
|
963
|
+
void operation.catch((err) => {
|
|
964
|
+
emitStructuredLog("warn", event, message, err);
|
|
965
|
+
});
|
|
966
|
+
}
|
|
958
967
|
/**
|
|
959
968
|
* Start the heartbeat and periodic cleanup tasks.
|
|
960
969
|
*/
|
|
@@ -987,26 +996,34 @@ var PersistentProcessRegistry = class {
|
|
|
987
996
|
this.cleanupInterval = null;
|
|
988
997
|
}
|
|
989
998
|
process.off("exit", this.onProcessExit);
|
|
990
|
-
|
|
999
|
+
this.runInBackground(
|
|
1000
|
+
this.syncToPersistent(),
|
|
1001
|
+
"process_registry.stop_sync_failed",
|
|
1002
|
+
"PersistentProcessRegistry: stop sync failed"
|
|
1003
|
+
);
|
|
991
1004
|
}
|
|
992
1005
|
/**
|
|
993
1006
|
* Register the main WrongStack process as protected.
|
|
994
1007
|
*/
|
|
995
1008
|
registerMainProcess() {
|
|
996
1009
|
const mainPid = process.pid;
|
|
997
|
-
this.
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
+
this.runInBackground(
|
|
1011
|
+
this.updatePersistentEntry({
|
|
1012
|
+
pid: mainPid,
|
|
1013
|
+
name: "wrongstack-main",
|
|
1014
|
+
command: process.argv.slice(0, 3).join(" "),
|
|
1015
|
+
startedAt: Date.now(),
|
|
1016
|
+
lastHeartbeat: Date.now(),
|
|
1017
|
+
instanceId: this.instanceId,
|
|
1018
|
+
hostname: os2.hostname(),
|
|
1019
|
+
protected: true,
|
|
1020
|
+
spawnMode: "main",
|
|
1021
|
+
parentPid: process.ppid,
|
|
1022
|
+
platform: process.platform
|
|
1023
|
+
}),
|
|
1024
|
+
"process_registry.main_register_failed",
|
|
1025
|
+
"PersistentProcessRegistry: failed to register main process"
|
|
1026
|
+
);
|
|
1010
1027
|
}
|
|
1011
1028
|
/**
|
|
1012
1029
|
* Register a spawned child process with the persistent registry.
|
|
@@ -1029,7 +1046,11 @@ var PersistentProcessRegistry = class {
|
|
|
1029
1046
|
if (sessionId) {
|
|
1030
1047
|
entry.sessionId = sessionId;
|
|
1031
1048
|
}
|
|
1032
|
-
this.
|
|
1049
|
+
this.runInBackground(
|
|
1050
|
+
this.updatePersistentEntry(entry),
|
|
1051
|
+
"process_registry.child_register_failed",
|
|
1052
|
+
"PersistentProcessRegistry: failed to register child process"
|
|
1053
|
+
);
|
|
1033
1054
|
}
|
|
1034
1055
|
/**
|
|
1035
1056
|
* Update or add an entry in the persistent registry.
|
|
@@ -1073,14 +1094,28 @@ var PersistentProcessRegistry = class {
|
|
|
1073
1094
|
heartbeat() {
|
|
1074
1095
|
if (this.isShuttingDown || this.heartbeatRunning) return;
|
|
1075
1096
|
this.heartbeatRunning = true;
|
|
1076
|
-
void this.syncToPersistent().
|
|
1097
|
+
void this.syncToPersistent().catch((err) => {
|
|
1098
|
+
emitStructuredLog(
|
|
1099
|
+
"warn",
|
|
1100
|
+
"process_registry.heartbeat_failed",
|
|
1101
|
+
"PersistentProcessRegistry: heartbeat failed",
|
|
1102
|
+
err
|
|
1103
|
+
);
|
|
1104
|
+
}).finally(() => {
|
|
1077
1105
|
this.heartbeatRunning = false;
|
|
1078
1106
|
});
|
|
1079
1107
|
}
|
|
1080
1108
|
cleanup() {
|
|
1081
1109
|
if (this.isShuttingDown || this.cleanupRunning) return;
|
|
1082
1110
|
this.cleanupRunning = true;
|
|
1083
|
-
void this.cleanupStaleEntries().
|
|
1111
|
+
void this.cleanupStaleEntries().catch((err) => {
|
|
1112
|
+
emitStructuredLog(
|
|
1113
|
+
"warn",
|
|
1114
|
+
"process_registry.periodic_cleanup_failed",
|
|
1115
|
+
"PersistentProcessRegistry: periodic cleanup failed",
|
|
1116
|
+
err
|
|
1117
|
+
);
|
|
1118
|
+
}).finally(() => {
|
|
1084
1119
|
this.cleanupRunning = false;
|
|
1085
1120
|
});
|
|
1086
1121
|
}
|