@wrongstack/tools 0.295.1 → 0.296.3
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/artifacts.d.ts +2 -0
- package/dist/browser/artifacts.d.ts.map +1 -1
- package/dist/browser/index.js +58 -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 +9516 -7476
- 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 +5217 -3503
- 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 +5136 -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 +3927 -3413
- 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 +72 -89
- 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 +9860 -7824
- 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 +9515 -7476
- 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/read.d.ts +31 -0
- package/dist/read.d.ts.map +1 -1
- package/dist/read.js +5434 -78
- package/dist/read.js.map +4 -4
- 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 +9517 -7477
- 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
package/dist/replace.js
CHANGED
|
@@ -275,9 +275,17 @@ function spawnRgFind(pattern, base) {
|
|
|
275
275
|
stdio: ["ignore", "pipe", "pipe"],
|
|
276
276
|
windowsHide: true
|
|
277
277
|
});
|
|
278
|
+
const MAX_BUF_CHARS = 8 * 1024 * 1024;
|
|
278
279
|
let buf = "";
|
|
280
|
+
let truncated = false;
|
|
279
281
|
child.stdout?.on("data", (chunk) => {
|
|
282
|
+
if (truncated) return;
|
|
280
283
|
buf += chunk.toString();
|
|
284
|
+
if (buf.length > MAX_BUF_CHARS) {
|
|
285
|
+
truncated = true;
|
|
286
|
+
buf = buf.slice(0, buf.lastIndexOf("\n") + 1);
|
|
287
|
+
child.kill();
|
|
288
|
+
}
|
|
281
289
|
});
|
|
282
290
|
return {
|
|
283
291
|
promise: new Promise((resolve2, reject) => {
|
package/dist/replace.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/replace.ts", "../src/_regex.ts", "../src/_util.ts"],
|
|
4
|
-
"sourcesContent": ["import { expectDefined } from '@wrongstack/core/utils';\nimport { spawn } from 'node:child_process';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport {\n atomicWrite,\n buildChildEnv,\n compileGlob,\n detectNewlineStyle,\n normalizeToLf,\n toStyle,\n unifiedDiff,\n} from '@wrongstack/core/utils';\nimport { ToolValidationError } from '@wrongstack/core/types';\nimport type { Context } from '@wrongstack/core/agent';\nimport type { Tool } from '@wrongstack/core/types';\nimport { compileUserRegex } from './_regex.js';\nimport { isBinaryBuffer, safeResolve, sha256hex } from './_util.js';\ninterface ReplaceInput {\n pattern: string;\n replacement: string;\n files: string | string[];\n glob?: string | undefined;\n replace_all?: boolean | undefined;\n dry_run?: boolean | undefined;\n}\n\ninterface ReplaceOutput {\n files_modified: number;\n total_replacements: number;\n results: { path: string; replacements: number; diff?: string | undefined }[];\n dry_run: boolean;\n}\n\nconst DEFAULT_IGNORE = ['node_modules', '.git', 'dist', 'build', '.next', 'coverage'];\n\nexport const replaceTool: Tool<ReplaceInput, ReplaceOutput> = {\n name: 'replace',\n category: 'Transform',\n description:\n 'Perform a search-and-replace across multiple files using a regex pattern. ' +\n 'This is a powerful bulk transformation tool. Dry-run is ON by default \u2014 set `dry_run: false` to apply changes.',\n usageHint:\n 'DANGEROUS IF USED CARELESSLY \u2014 review the diff output carefully.\\n\\n' +\n 'Recommended workflow:\\n' +\n '1. Run without `dry_run: false` first to see exactly what would change (dry-run is the default).\\n' +\n '2. Review the diff output, then re-run with `dry_run: false` to apply.\\n' +\n '3. Use a specific enough `pattern` (and `glob` / `files`) to avoid accidental broad changes.\\n' +\n '4. `replace_all` controls whether only the first match per file or all matches are replaced.\\n' +\n 'This tool is excellent for large-scale refactors (renaming, import updates, etc.) but must be used with caution.',\n permission: 'confirm',\n mutating: true,\n capabilities: ['fs.write'],\n icon: 'edit',\n timeoutMs: 30_000,\n inputSchema: {\n type: 'object',\n properties: {\n pattern: { type: 'string', description: 'Regex pattern to match' },\n replacement: { type: 'string', description: 'Replacement string' },\n files: {\n type: 'string',\n description: 'File(s) to target: single path, comma-separated list, or glob pattern',\n },\n glob: { type: 'string', description: 'Additional glob filter (e.g. \"*.ts\")' },\n replace_all: {\n type: 'boolean',\n description: 'Replace all occurrences in each file (default: true)',\n },\n dry_run: { type: 'boolean', description: 'Preview changes without writing (default: true)' },\n },\n required: ['pattern', 'replacement', 'files'],\n },\n async execute(input: ReplaceInput, ctx: Context) {\n if (!input?.pattern) {\n throw new ToolValidationError({\n message: 'replace: pattern is required',\n field: 'pattern',\n });\n }\n if (input.replacement === undefined) {\n throw new ToolValidationError({\n message: 'replace: replacement is required',\n field: 'replacement',\n });\n }\n if (!input?.files) {\n throw new ToolValidationError({\n message: 'replace: files is required',\n field: 'files',\n });\n }\n\n const replaceAll = input.replace_all ?? true;\n // Always compile with 'g' so matchAll() works \u2014 matchAll throws\n // TypeError on non-global regexes. The replaceAll flag controls\n // how many matches we act on, not whether the regex is global.\n const compiled = compileUserRegex(input.pattern, 'g');\n if (!compiled.ok) {\n throw new ToolValidationError({\n message: `replace: ${compiled.reason}`,\n field: 'pattern',\n });\n }\n const re = compiled.regex;\n const globRe = input.glob ? compileGlob(input.glob) : null;\n const dryRun = input.dry_run ?? true;\n\n const filesInput = Array.isArray(input.files) ? input.files.join(',') : input.files;\n const fileList = await resolveFiles(filesInput, ctx, globRe);\n\n // Resolve the project root through realpath ONCE so the sandbox check\n // below compares like-for-like with realpath(file). The project root\n // itself can be a symlink or short name \u2014 e.g. macOS temp dirs live under\n // /var -> /private/var, and Windows CI runners expose an 8.3 short name\n // (C:\\Users\\RUNNER~1\\...). Comparing realpath(file) against the raw root\n // then makes every legitimately-inside file look \"outside\" and skips it.\n const realRoot = await fs.realpath(ctx.projectRoot).catch(() => ctx.projectRoot);\n\n const results: ReplaceOutput['results'] = [];\n let totalReplacements = 0;\n\n for (const absPath of fileList) {\n // Use lstat to detect symlinks. resolveFiles already applies\n // safeResolve, but a symlink with a target outside the project\n // root would still pass that string check \u2014 explicitly skip it\n // so we never read or write through a link.\n const lstat = await fs.lstat(absPath).catch((err) => {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;\n /* v8 ignore next -- non-ENOENT lstat failure (EACCES etc.) is a defensive rethrow. */\n throw err;\n });\n if (!lstat?.isFile()) continue;\n if (lstat.isSymbolicLink()) continue;\n\n // Cross-check via realpath: if the resolved target lives outside the\n // project root (e.g. a bind mount or a parent-dir traversal we missed),\n // skip rather than rewrite through it.\n let realPath: string;\n try {\n realPath = await fs.realpath(absPath);\n } catch {\n /* v8 ignore next -- realpath failing after a successful lstat is a TOCTOU race; defensive. */\n continue;\n }\n const rel = path.relative(realRoot, realPath);\n if (rel.startsWith('..') || path.isAbsolute(rel)) continue;\n\n // Now stat the real target so we use its mode for atomicWrite.\n const stat = await fs.stat(realPath).catch(() => null);\n if (!stat?.isFile()) continue;\n\n let content: string;\n try {\n const buf = await fs.readFile(realPath);\n if (isBinaryBuffer(buf)) continue;\n content = buf.toString('utf8');\n } catch {\n /* v8 ignore next -- readFile failing after a successful stat is a TOCTOU race; defensive. */\n continue;\n }\n\n const style = detectNewlineStyle(content);\n const contentLf = normalizeToLf(content);\n re.lastIndex = 0;\n const allMatches = [...contentLf.matchAll(re)];\n if (allMatches.length === 0) continue;\n\n // When replace_all is false, only act on the first match.\n const matches = replaceAll ? allMatches : allMatches.slice(0, 1);\n const count = matches.length;\n\n // Rebuild: splice the replacement into each match position from\n // right to left so earlier indices stay valid.\n let newContentLf = contentLf;\n for (let i = matches.length - 1; i >= 0; i--) {\n const m = expectDefined(matches[i]);\n newContentLf =\n newContentLf.slice(0, m.index) +\n input.replacement +\n newContentLf.slice(expectDefined(m.index) + m[0].length);\n }\n re.lastIndex = 0;\n totalReplacements += count;\n\n if (!dryRun) {\n const newContent = toStyle(newContentLf, style);\n // Write to the real path (already validated inside project root)\n // so atomicWrite's temp-and-rename can't be redirected through a\n // freshly-planted symlink at absPath.\n await atomicWrite(realPath, newContent, { mode: stat.mode & 0o777 });\n // Same bookkeeping as `edit`: record the new mtime + hash (tagged\n // 'write' so the permission bypass does not widen) so a later `edit`\n // of this file doesn't trip the stale-read guard on our own write,\n // and record the change for session rewind. Optional calls: embedders\n // may hand in a duck-typed Context without these members.\n const written = await fs.stat(realPath).catch(() => null);\n if (written) {\n ctx.recordRead?.(realPath, written.mtimeMs, 'write', sha256hex(newContent));\n }\n ctx.session?.recordFileChange?.({\n path: realPath,\n action: 'modified',\n before: content,\n after: newContent,\n });\n }\n\n const diff =\n dryRun || matches.length > 0\n ? unifiedDiff(content, toStyle(newContentLf, style), {\n fromFile: absPath,\n toFile: absPath,\n })\n : undefined;\n\n results.push({\n path: absPath,\n replacements: matches.length,\n diff,\n });\n }\n\n return {\n files_modified: results.length,\n total_replacements: totalReplacements,\n results,\n dry_run: dryRun,\n };\n },\n};\n\nasync function resolveFiles(\n filesInput: string,\n ctx: Context,\n extraGlob?: RegExp | null | undefined,\n): Promise<string[]> {\n const base = ctx.cwd;\n const normalized = filesInput.trim();\n\n if (normalized.startsWith('**/') || normalized.startsWith('*') || normalized.includes('**')) {\n return await globFiles(normalized, base, extraGlob);\n }\n\n const parts = normalized\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n const resolved: string[] = [];\n\n for (const p of parts) {\n const absPath = safeResolve(p, ctx);\n const stat = await fs.stat(absPath).catch(() => null);\n if (stat?.isFile()) {\n resolved.push(absPath);\n }\n }\n\n return resolved;\n}\n\nasync function globFiles(\n pattern: string,\n base: string,\n extraGlob?: RegExp | null | undefined,\n): Promise<string[]> {\n\n const rgAvailable = await checkRg();\n if (rgAvailable) {\n try {\n const { promise } = spawnRgFind(pattern, base);\n return await promise;\n } catch {\n // fall through\n }\n }\n\n return await globNative(pattern, base, extraGlob);\n}\n\nfunction checkRg(): Promise<boolean> {\n return new Promise((resolve) => {\n try {\n const p = spawn('rg', ['--version'], { env: buildChildEnv(), stdio: 'ignore', windowsHide: true });\n p.on('error', () => resolve(false));\n p.on('close', (code) => resolve(code === 0));\n } catch {\n resolve(false);\n }\n });\n}\n\nfunction spawnRgFind(pattern: string, base: string): { promise: Promise<string[]> } {\n const args = ['--files', '--glob', pattern, base];\n // 30-second safety net to prevent zombie rg processes. Unlike the main\n // grep tool, glob file enumeration is fast and should never need more time.\n const child = spawn('rg', args, {\n signal: AbortSignal.timeout(30_000),\n env: buildChildEnv(),\n stdio: ['ignore', 'pipe', 'pipe'],\n windowsHide: true,\n });\n let buf = '';\n child.stdout?.on('data', (chunk: Buffer) => {\n buf += chunk.toString();\n });\n return {\n promise: new Promise((resolve, reject) => {\n child.on('error', reject);\n child.on('close', () => {\n resolve(buf.split('\\n').filter(Boolean));\n });\n }),\n };\n}\n\nasync function globNative(\n pattern: string,\n base: string,\n extraGlob?: RegExp | null | undefined,\n): Promise<string[]> {\n const results: string[] = [];\n const globRe = compileGlob(pattern);\n\n const walk = async (dir: string): Promise<void> => {\n let entries: import('node:fs').Dirent[];\n try {\n entries = await fs.readdir(dir, { withFileTypes: true });\n } catch {\n /* v8 ignore next -- unreadable directory during the walk; defensive. */\n return;\n }\n for (const e of entries) {\n if (DEFAULT_IGNORE.includes(e.name)) continue;\n const full = path.join(dir, e.name);\n // Dirent.isSymbolicLink() uses readdir's d_type, which may not detect\n // directory symlinks on Windows (d_type = DT_UNKNOWN). Defensive stat\n // call: skip any entry whose lstat shows a symlink \u2014 file or directory.\n try {\n const stat = await fs.lstat(full);\n if (stat.isSymbolicLink()) continue;\n } catch {\n // lstat fails for very unusual entries (e.g. broken symlinks to deleted\n // files on NFS); skip safely rather than surfacing an error.\n /* v8 ignore next -- lstat failing on a readdir entry is a rare NFS/race case; defensive. */\n continue;\n }\n if (e.isDirectory()) {\n await walk(full);\n } else if (e.isFile()) {\n const name = e.name;\n if (globRe.test(name) || globRe.test(full)) {\n if (extraGlob && !extraGlob.test(name) && !extraGlob.test(full)) continue;\n results.push(full);\n }\n globRe.lastIndex = 0;\n if (extraGlob) extraGlob.lastIndex = 0;\n }\n }\n };\n\n await walk(base);\n return results;\n}\n", "/**\n * Compile a user-supplied regex with conservative bounds against ReDoS.\n *\n * Node's regex engine (V8) is backtracking-based and cannot interrupt a\n * synchronous match \u2014 a pattern like `(a+)+$` against a sufficiently long\n * line will pin a worker for seconds. The executor's outer `timeoutMs` only\n * fires between async boundaries, so a long regex eval inside a sync loop\n * is uninterruptible.\n *\n * We can't fully prevent ReDoS without an alternative engine (re2-wasm), but\n * we can sharply limit the blast radius:\n *\n * 1. Cap pattern length \u2014 practically all legitimate user patterns are\n * under 256 characters. A 4 KB pattern is almost certainly malicious\n * or a copy-paste accident.\n * 2. Reject patterns containing the most obvious super-linear structures.\n * This is a coarse filter (false-positives are likely; we accept that\n * for hostile-input contexts).\n *\n * Callers should additionally bound the *subject* length (e.g. by capping\n * line size before matching).\n */\n\nconst MAX_PATTERN_LEN = 256;\n\n// Heuristics for catastrophic-backtracking constructs. Not exhaustive; bias\n// toward false-positives in tools that accept LLM-generated input.\nconst DANGEROUS_PATTERNS: ReadonlyArray<RegExp> = [\n // (a+)+, (.*)+, etc \u2014 nested quantifier on a group with internal quantifier\n /(\\([^)]*[+*][^)]*\\))[+*]/,\n /(\\(\\?:[^)]*[+*][^)]*\\))[+*]/,\n // Adjacent quantifiers: a++ a*+\n /[+*]{2,}/,\n // Quantifier on alternation with length 2+\n /\\([^|)]+\\|[^)]+\\)[+*][+*]/,\n // Greedy quantifier inside lookahead/lookbehind \u2014 (?!.*a+)\n /[([][^)\\]]*[+*][^)\\]]*[)\\]][^)]*\\?\\??/,\n];\n\nexport interface CompileResult {\n ok: true;\n regex: RegExp;\n}\n\nexport interface CompileFail {\n ok: false;\n reason: string;\n}\n\nexport function compileUserRegex(pattern: string, flags: string): CompileResult | CompileFail {\n if (typeof pattern !== 'string') {\n return { ok: false, reason: 'pattern must be a string' };\n }\n if (pattern.length === 0) {\n return { ok: false, reason: 'pattern is empty' };\n }\n if (pattern.length > MAX_PATTERN_LEN) {\n return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };\n }\n for (const rx of DANGEROUS_PATTERNS) {\n if (rx.test(pattern)) {\n return {\n ok: false,\n reason:\n 'pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers',\n };\n }\n }\n try {\n return { ok: true, regex: new RegExp(pattern, flags) };\n } catch (err) {\n return {\n ok: false,\n reason: err instanceof Error ? err.message : 'invalid regex',\n };\n }\n}\n\n/**\n * Truncate a subject line to a safe length for synchronous regex eval.\n * The cap is conservative; tools that need exact-line matching against very\n * long lines should use ripgrep externally rather than the native walker.\n */\nexport const MAX_SUBJECT_LEN = 64 * 1024;\n\nexport function capSubject(line: string): string {\n return line.length > MAX_SUBJECT_LEN ? line.slice(0, MAX_SUBJECT_LEN) : line;\n}\n", "import { createHash } from 'node:crypto';\nimport * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport * as Core from '@wrongstack/core/utils';\nimport type { Context } from '@wrongstack/core/agent';\n\n/**\n * sha-256 hex of a UTF-8 string. Used by the file tools to record a content\n * hash alongside the mtime in `ctx.recordRead` \u2014 the hash is the authoritative\n * staleness arbiter for `edit` (mtime has a 2 s tolerance window on Windows).\n */\nexport function sha256hex(content: string): string {\n return createHash('sha256').update(content, 'utf8').digest('hex');\n}\n/** Detected package manager for a project directory. */\nexport type PackageManager = 'pnpm' | 'yarn' | 'npm';\n\n/**\n * Detect the project's package manager by inspecting lockfiles in `cwd`.\n * Order: pnpm \u2192 yarn \u2192 npm (default). Missing or unreadable directories fall\n * back to `npm` rather than throwing, so a `safeResolve`-checked cwd that\n * happens to be empty never aborts the tool.\n */\nexport async function detectPackageManager(cwd: string): Promise<PackageManager> {\n const { stat } = await import('node:fs/promises');\n try {\n await stat(`${cwd}/pnpm-lock.yaml`);\n return 'pnpm';\n } catch {\n /* not pnpm */\n }\n try {\n await stat(`${cwd}/yarn.lock`);\n return 'yarn';\n } catch {\n /* not yarn */\n }\n return 'npm';\n}\n\nexport function resolvePath(input: string, ctx: Context): string {\n return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.workingDir ?? ctx.cwd, input);\n}\n\n/**\n * Roots every file tool may always reach, even in restricted mode: the\n * project root and the user-global `~/.wrongstack` directory (config, memory,\n * sessions, skills). `~/.wrongstack` honors the `WRONGSTACK_HOME` override.\n */\nfunction allowedRoots(ctx: Context): string[] {\n return [path.resolve(ctx.projectRoot), path.resolve(Core.wstackGlobalRoot())];\n}\n\n/** True if `target` is `root` itself or nested inside any of `roots`. */\nfunction isInsideAny(target: string, roots: string[]): boolean {\n return roots.some((root) => {\n const rel = path.relative(root, target);\n return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));\n });\n}\n\nexport function ensureInsideRoot(absPath: string, ctx: Context): string {\n const target = path.resolve(absPath);\n // Unrestricted filesystem access: skip the project-root containment check.\n if (ctx.allowOutsideProjectRoot) return target;\n if (isInsideAny(target, allowedRoots(ctx))) return target;\n throw new Error(`Path \"${absPath}\" is outside project root \"${path.resolve(ctx.projectRoot)}\"`);\n}\n\nexport function safeResolve(input: string, ctx: Context): string {\n return ensureInsideRoot(resolvePath(input, ctx), ctx);\n}\n\n/**\n * Defense against in-root\u2192out-of-root symlink escape (CWE-59). `safeResolve`\n * only does a syntactic `../` check, so a symlink that lives *inside* the\n * project root but points outside still passes it. This resolves the path\n * through `fs.realpath` and re-verifies containment against the realpath of\n * the project root (comparing like-for-like, since the root itself may be a\n * symlink \u2014 macOS `/var`\u2192`/private/var`, Windows 8.3 short names). For a path\n * that does not exist yet (e.g. a `write` to a new file) the nearest existing\n * ancestor directory is checked instead. Throws if the real target escapes.\n *\n * Mirrors the per-file guard already used in `replace.ts`/`grep.ts`; applied\n * to single-file `read`/`edit`/`write` it throws (rather than skips) because\n * the caller named exactly one file.\n */\nexport async function assertRealInsideRoot(absPath: string, ctx: Context): Promise<void> {\n // Unrestricted filesystem access: no symlink-escape check to perform.\n if (ctx.allowOutsideProjectRoot) return;\n // Compare like-for-like against the realpath of each always-allowed root\n // (project root + ~/.wrongstack), since a root may itself be a symlink.\n const realRoots = await Promise.all(\n allowedRoots(ctx).map((r) => fsp.realpath(r).catch(() => path.resolve(r))),\n );\n let probe = absPath;\n for (;;) {\n let real: string;\n try {\n real = await fsp.realpath(probe);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n const parent = path.dirname(probe);\n if (parent === probe) return; // reached fs root without escaping\n probe = parent;\n continue;\n }\n throw err;\n }\n if (isInsideAny(real, realRoots)) return;\n throw new Error(\n `Path \"${absPath}\" resolves through a symlink outside project root \"${realRoots[0]}\"`,\n );\n }\n}\n\n/** `safeResolve` + symlink realpath containment check. Async. */\nexport async function safeResolveReal(input: string, ctx: Context): Promise<string> {\n const abs = safeResolve(input, ctx);\n await assertRealInsideRoot(abs, ctx);\n return abs;\n}\n\nexport function truncateMiddle(s: string, max: number): string {\n if (Buffer.byteLength(s, 'utf8') <= max) return s;\n const half = Math.floor(max / 2);\n return (\n s.slice(0, half) +\n `\\n\u2026[truncated ${Buffer.byteLength(s, 'utf8') - max} bytes from middle]\u2026\\n` +\n s.slice(-half)\n );\n}\n\nexport function isBinaryBuffer(buf: Buffer): boolean {\n const len = Math.min(buf.length, 8192);\n for (let i = 0; i < len; i++) {\n if (buf[i] === 0) return true;\n }\n return false;\n}\n\n// \u2500\u2500\u2500 Command-output normalization (token-saving) \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// Raw process output is full of tokens the model gains nothing from: ANSI\n// escapes, carriage-return progress spam, runs of identical warning lines, and\n// huge tails of build noise. These helpers strip that noise before the output\n// reaches the LLM. They are scoped to COMMAND tools (bash/git/exec and the\n// _spawn-stream consumers) \u2014 never applied to structured/code outputs.\n\n/** Unified byte cap for all command tool output fed to the model. */\nexport const COMMAND_OUTPUT_MAX_BYTES = 32_768;\n\n/** Runs of >= this many identical consecutive lines are collapsed. */\nconst REPEAT_RUN_THRESHOLD = 3;\n\n/**\n * Collapse carriage-return overwrites the way a terminal would: `\\r\\n` becomes\n * `\\n`, and a bare `\\r` (progress redraw) keeps only the text after the LAST\n * `\\r` on its physical line. Without this, a single progress bar that redraws\n * 200 times explodes into 200 lines.\n */\nexport function collapseCarriageReturns(text: string): string {\n const lf = text.replace(/\\r\\n/g, '\\n');\n if (!lf.includes('\\r')) return lf;\n return lf\n .split('\\n')\n .map((line) => (line.includes('\\r') ? line.slice(line.lastIndexOf('\\r') + 1) : line))\n .join('\\n');\n}\n\n/**\n * Collapse a run of `minRun`+ identical consecutive lines into the line once\n * plus a marker. Consecutive-only \u2014 it never reorders or dedups non-adjacent\n * lines, so diffs/source stay intact.\n */\nexport function collapseConsecutiveDuplicates(text: string, minRun = REPEAT_RUN_THRESHOLD): string {\n const lines = text.split('\\n');\n const out: string[] = [];\n let i = 0;\n while (i < lines.length) {\n let j = i + 1;\n while (j < lines.length && lines[j] === lines[i]) j++;\n const run = j - i;\n if (run >= minRun) {\n out.push(lines[i]!, `\u2026 \u27E8repeated ${run}\u00D7\u27E9`);\n } else {\n for (let k = i; k < j; k++) out.push(lines[k]!);\n }\n i = j;\n }\n return out.join('\\n');\n}\n\n/** Largest prefix of `s` whose UTF-8 byte length is <= `maxBytes`. */\nfunction takeHeadBytes(s: string, maxBytes: number): string {\n if (maxBytes <= 0) return '';\n /* v8 ignore next -- only caller (truncateHeadTail) passes a budget smaller than s; defensive. */\n if (Buffer.byteLength(s, 'utf8') <= maxBytes) return s;\n let lo = 0;\n let hi = s.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (Buffer.byteLength(s.slice(0, mid), 'utf8') <= maxBytes) lo = mid;\n else hi = mid - 1;\n }\n return s.slice(0, lo);\n}\n\n/** Largest suffix of `s` whose UTF-8 byte length is <= `maxBytes`. */\nfunction takeTailBytes(s: string, maxBytes: number): string {\n if (maxBytes <= 0) return '';\n /* v8 ignore next -- only caller (truncateHeadTail) passes a budget smaller than s; defensive. */\n if (Buffer.byteLength(s, 'utf8') <= maxBytes) return s;\n let lo = 0;\n let hi = s.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (Buffer.byteLength(s.slice(s.length - mid), 'utf8') <= maxBytes) lo = mid;\n else hi = mid - 1;\n }\n return s.slice(s.length - lo);\n}\n\n/**\n * Truncate to `maxBytes` keeping BOTH ends \u2014 the head (what ran / early context)\n * and the tail (errors and summaries usually land last), biased ~45/55 toward\n * the tail. The result never exceeds `maxBytes`.\n */\nexport function truncateHeadTail(s: string, maxBytes: number): string {\n const total = Buffer.byteLength(s, 'utf8');\n if (total <= maxBytes) return s;\n // Reserve a fixed allowance for the marker so the final string can't exceed\n // the cap even though the dropped-byte count's digit width varies.\n const MARKER_RESERVE = 64;\n const avail = Math.max(0, maxBytes - MARKER_RESERVE);\n const headBudget = Math.floor(avail * 0.45);\n const head = takeHeadBytes(s, headBudget);\n const tail = takeTailBytes(s, avail - Buffer.byteLength(head, 'utf8'));\n const kept = Buffer.byteLength(head, 'utf8') + Buffer.byteLength(tail, 'utf8');\n return `${head}\\n\u2026[truncated ${total - kept} bytes]\u2026\\n${tail}`;\n}\n\n/**\n * Full token-saving pipeline for command tool output: strip ANSI \u2192 collapse\n * carriage-return progress \u2192 trim trailing whitespace \u2192 collapse identical\n * consecutive lines \u2192 squeeze blank-line runs \u2192 head+tail truncate to the cap.\n */\nexport function normalizeCommandOutput(\n raw: string,\n opts: { maxBytes?: number | undefined } = {},\n): string {\n if (!raw) return raw;\n let text = Core.stripAnsi(raw);\n text = collapseCarriageReturns(text);\n text = text.replace(/[ \\t]+$/gm, ''); // trailing whitespace per line\n text = collapseConsecutiveDuplicates(text);\n text = text.replace(/\\n{3,}/g, '\\n\\n'); // >=2 blank lines \u2192 1\n return truncateHeadTail(text, opts.maxBytes ?? COMMAND_OUTPUT_MAX_BYTES);\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { expectDefined } from '@wrongstack/core/utils';\nimport { spawn } from 'node:child_process';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport {\n atomicWrite,\n buildChildEnv,\n compileGlob,\n detectNewlineStyle,\n normalizeToLf,\n toStyle,\n unifiedDiff,\n} from '@wrongstack/core/utils';\nimport { ToolValidationError } from '@wrongstack/core/types';\nimport type { Context } from '@wrongstack/core/agent';\nimport type { Tool } from '@wrongstack/core/types';\nimport { compileUserRegex } from './_regex.js';\nimport { isBinaryBuffer, safeResolve, sha256hex } from './_util.js';\ninterface ReplaceInput {\n pattern: string;\n replacement: string;\n files: string | string[];\n glob?: string | undefined;\n replace_all?: boolean | undefined;\n dry_run?: boolean | undefined;\n}\n\ninterface ReplaceOutput {\n files_modified: number;\n total_replacements: number;\n results: { path: string; replacements: number; diff?: string | undefined }[];\n dry_run: boolean;\n}\n\nconst DEFAULT_IGNORE = ['node_modules', '.git', 'dist', 'build', '.next', 'coverage'];\n\nexport const replaceTool: Tool<ReplaceInput, ReplaceOutput> = {\n name: 'replace',\n category: 'Transform',\n description:\n 'Perform a search-and-replace across multiple files using a regex pattern. ' +\n 'This is a powerful bulk transformation tool. Dry-run is ON by default \u2014 set `dry_run: false` to apply changes.',\n usageHint:\n 'DANGEROUS IF USED CARELESSLY \u2014 review the diff output carefully.\\n\\n' +\n 'Recommended workflow:\\n' +\n '1. Run without `dry_run: false` first to see exactly what would change (dry-run is the default).\\n' +\n '2. Review the diff output, then re-run with `dry_run: false` to apply.\\n' +\n '3. Use a specific enough `pattern` (and `glob` / `files`) to avoid accidental broad changes.\\n' +\n '4. `replace_all` controls whether only the first match per file or all matches are replaced.\\n' +\n 'This tool is excellent for large-scale refactors (renaming, import updates, etc.) but must be used with caution.',\n permission: 'confirm',\n mutating: true,\n capabilities: ['fs.write'],\n icon: 'edit',\n timeoutMs: 30_000,\n inputSchema: {\n type: 'object',\n properties: {\n pattern: { type: 'string', description: 'Regex pattern to match' },\n replacement: { type: 'string', description: 'Replacement string' },\n files: {\n type: 'string',\n description: 'File(s) to target: single path, comma-separated list, or glob pattern',\n },\n glob: { type: 'string', description: 'Additional glob filter (e.g. \"*.ts\")' },\n replace_all: {\n type: 'boolean',\n description: 'Replace all occurrences in each file (default: true)',\n },\n dry_run: { type: 'boolean', description: 'Preview changes without writing (default: true)' },\n },\n required: ['pattern', 'replacement', 'files'],\n },\n async execute(input: ReplaceInput, ctx: Context) {\n if (!input?.pattern) {\n throw new ToolValidationError({\n message: 'replace: pattern is required',\n field: 'pattern',\n });\n }\n if (input.replacement === undefined) {\n throw new ToolValidationError({\n message: 'replace: replacement is required',\n field: 'replacement',\n });\n }\n if (!input?.files) {\n throw new ToolValidationError({\n message: 'replace: files is required',\n field: 'files',\n });\n }\n\n const replaceAll = input.replace_all ?? true;\n // Always compile with 'g' so matchAll() works \u2014 matchAll throws\n // TypeError on non-global regexes. The replaceAll flag controls\n // how many matches we act on, not whether the regex is global.\n const compiled = compileUserRegex(input.pattern, 'g');\n if (!compiled.ok) {\n throw new ToolValidationError({\n message: `replace: ${compiled.reason}`,\n field: 'pattern',\n });\n }\n const re = compiled.regex;\n const globRe = input.glob ? compileGlob(input.glob) : null;\n const dryRun = input.dry_run ?? true;\n\n const filesInput = Array.isArray(input.files) ? input.files.join(',') : input.files;\n const fileList = await resolveFiles(filesInput, ctx, globRe);\n\n // Resolve the project root through realpath ONCE so the sandbox check\n // below compares like-for-like with realpath(file). The project root\n // itself can be a symlink or short name \u2014 e.g. macOS temp dirs live under\n // /var -> /private/var, and Windows CI runners expose an 8.3 short name\n // (C:\\Users\\RUNNER~1\\...). Comparing realpath(file) against the raw root\n // then makes every legitimately-inside file look \"outside\" and skips it.\n const realRoot = await fs.realpath(ctx.projectRoot).catch(() => ctx.projectRoot);\n\n const results: ReplaceOutput['results'] = [];\n let totalReplacements = 0;\n\n for (const absPath of fileList) {\n // Use lstat to detect symlinks. resolveFiles already applies\n // safeResolve, but a symlink with a target outside the project\n // root would still pass that string check \u2014 explicitly skip it\n // so we never read or write through a link.\n const lstat = await fs.lstat(absPath).catch((err) => {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;\n /* v8 ignore next -- non-ENOENT lstat failure (EACCES etc.) is a defensive rethrow. */\n throw err;\n });\n if (!lstat?.isFile()) continue;\n if (lstat.isSymbolicLink()) continue;\n\n // Cross-check via realpath: if the resolved target lives outside the\n // project root (e.g. a bind mount or a parent-dir traversal we missed),\n // skip rather than rewrite through it.\n let realPath: string;\n try {\n realPath = await fs.realpath(absPath);\n } catch {\n /* v8 ignore next -- realpath failing after a successful lstat is a TOCTOU race; defensive. */\n continue;\n }\n const rel = path.relative(realRoot, realPath);\n if (rel.startsWith('..') || path.isAbsolute(rel)) continue;\n\n // Now stat the real target so we use its mode for atomicWrite.\n const stat = await fs.stat(realPath).catch(() => null);\n if (!stat?.isFile()) continue;\n\n let content: string;\n try {\n const buf = await fs.readFile(realPath);\n if (isBinaryBuffer(buf)) continue;\n content = buf.toString('utf8');\n } catch {\n /* v8 ignore next -- readFile failing after a successful stat is a TOCTOU race; defensive. */\n continue;\n }\n\n const style = detectNewlineStyle(content);\n const contentLf = normalizeToLf(content);\n re.lastIndex = 0;\n const allMatches = [...contentLf.matchAll(re)];\n if (allMatches.length === 0) continue;\n\n // When replace_all is false, only act on the first match.\n const matches = replaceAll ? allMatches : allMatches.slice(0, 1);\n const count = matches.length;\n\n // Rebuild: splice the replacement into each match position from\n // right to left so earlier indices stay valid.\n let newContentLf = contentLf;\n for (let i = matches.length - 1; i >= 0; i--) {\n const m = expectDefined(matches[i]);\n newContentLf =\n newContentLf.slice(0, m.index) +\n input.replacement +\n newContentLf.slice(expectDefined(m.index) + m[0].length);\n }\n re.lastIndex = 0;\n totalReplacements += count;\n\n if (!dryRun) {\n const newContent = toStyle(newContentLf, style);\n // Write to the real path (already validated inside project root)\n // so atomicWrite's temp-and-rename can't be redirected through a\n // freshly-planted symlink at absPath.\n await atomicWrite(realPath, newContent, { mode: stat.mode & 0o777 });\n // Same bookkeeping as `edit`: record the new mtime + hash (tagged\n // 'write' so the permission bypass does not widen) so a later `edit`\n // of this file doesn't trip the stale-read guard on our own write,\n // and record the change for session rewind. Optional calls: embedders\n // may hand in a duck-typed Context without these members.\n const written = await fs.stat(realPath).catch(() => null);\n if (written) {\n ctx.recordRead?.(realPath, written.mtimeMs, 'write', sha256hex(newContent));\n }\n ctx.session?.recordFileChange?.({\n path: realPath,\n action: 'modified',\n before: content,\n after: newContent,\n });\n }\n\n const diff =\n dryRun || matches.length > 0\n ? unifiedDiff(content, toStyle(newContentLf, style), {\n fromFile: absPath,\n toFile: absPath,\n })\n : undefined;\n\n results.push({\n path: absPath,\n replacements: matches.length,\n diff,\n });\n }\n\n return {\n files_modified: results.length,\n total_replacements: totalReplacements,\n results,\n dry_run: dryRun,\n };\n },\n};\n\nasync function resolveFiles(\n filesInput: string,\n ctx: Context,\n extraGlob?: RegExp | null | undefined,\n): Promise<string[]> {\n const base = ctx.cwd;\n const normalized = filesInput.trim();\n\n if (normalized.startsWith('**/') || normalized.startsWith('*') || normalized.includes('**')) {\n return await globFiles(normalized, base, extraGlob);\n }\n\n const parts = normalized\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n const resolved: string[] = [];\n\n for (const p of parts) {\n const absPath = safeResolve(p, ctx);\n const stat = await fs.stat(absPath).catch(() => null);\n if (stat?.isFile()) {\n resolved.push(absPath);\n }\n }\n\n return resolved;\n}\n\nasync function globFiles(\n pattern: string,\n base: string,\n extraGlob?: RegExp | null | undefined,\n): Promise<string[]> {\n\n const rgAvailable = await checkRg();\n if (rgAvailable) {\n try {\n const { promise } = spawnRgFind(pattern, base);\n return await promise;\n } catch {\n // fall through\n }\n }\n\n return await globNative(pattern, base, extraGlob);\n}\n\nfunction checkRg(): Promise<boolean> {\n return new Promise((resolve) => {\n try {\n const p = spawn('rg', ['--version'], { env: buildChildEnv(), stdio: 'ignore', windowsHide: true });\n p.on('error', () => resolve(false));\n p.on('close', (code) => resolve(code === 0));\n } catch {\n resolve(false);\n }\n });\n}\n\nfunction spawnRgFind(pattern: string, base: string): { promise: Promise<string[]> } {\n const args = ['--files', '--glob', pattern, base];\n // 30-second safety net to prevent zombie rg processes. Unlike the main\n // grep tool, glob file enumeration is fast and should never need more time.\n const child = spawn('rg', args, {\n signal: AbortSignal.timeout(30_000),\n env: buildChildEnv(),\n stdio: ['ignore', 'pipe', 'pipe'],\n windowsHide: true,\n });\n // Bound the capture like grep.ts and bash.ts already do. A pathological glob\n // over a huge tree could otherwise accumulate the whole file listing in one\n // unbounded string before it is ever split.\n const MAX_BUF_CHARS = 8 * 1024 * 1024;\n let buf = '';\n let truncated = false;\n child.stdout?.on('data', (chunk: Buffer) => {\n if (truncated) return;\n buf += chunk.toString();\n if (buf.length > MAX_BUF_CHARS) {\n truncated = true;\n // Drop the partial trailing path so we never emit a half-written name.\n buf = buf.slice(0, buf.lastIndexOf('\\n') + 1);\n child.kill();\n }\n });\n return {\n promise: new Promise((resolve, reject) => {\n child.on('error', reject);\n child.on('close', () => {\n resolve(buf.split('\\n').filter(Boolean));\n });\n }),\n };\n}\n\nasync function globNative(\n pattern: string,\n base: string,\n extraGlob?: RegExp | null | undefined,\n): Promise<string[]> {\n const results: string[] = [];\n const globRe = compileGlob(pattern);\n\n const walk = async (dir: string): Promise<void> => {\n let entries: import('node:fs').Dirent[];\n try {\n entries = await fs.readdir(dir, { withFileTypes: true });\n } catch {\n /* v8 ignore next -- unreadable directory during the walk; defensive. */\n return;\n }\n for (const e of entries) {\n if (DEFAULT_IGNORE.includes(e.name)) continue;\n const full = path.join(dir, e.name);\n // Dirent.isSymbolicLink() uses readdir's d_type, which may not detect\n // directory symlinks on Windows (d_type = DT_UNKNOWN). Defensive stat\n // call: skip any entry whose lstat shows a symlink \u2014 file or directory.\n try {\n const stat = await fs.lstat(full);\n if (stat.isSymbolicLink()) continue;\n } catch {\n // lstat fails for very unusual entries (e.g. broken symlinks to deleted\n // files on NFS); skip safely rather than surfacing an error.\n /* v8 ignore next -- lstat failing on a readdir entry is a rare NFS/race case; defensive. */\n continue;\n }\n if (e.isDirectory()) {\n await walk(full);\n } else if (e.isFile()) {\n const name = e.name;\n if (globRe.test(name) || globRe.test(full)) {\n if (extraGlob && !extraGlob.test(name) && !extraGlob.test(full)) continue;\n results.push(full);\n }\n globRe.lastIndex = 0;\n if (extraGlob) extraGlob.lastIndex = 0;\n }\n }\n };\n\n await walk(base);\n return results;\n}\n", "/**\n * Compile a user-supplied regex with conservative bounds against ReDoS.\n *\n * Node's regex engine (V8) is backtracking-based and cannot interrupt a\n * synchronous match \u2014 a pattern like `(a+)+$` against a sufficiently long\n * line will pin a worker for seconds. The executor's outer `timeoutMs` only\n * fires between async boundaries, so a long regex eval inside a sync loop\n * is uninterruptible.\n *\n * We can't fully prevent ReDoS without an alternative engine (re2-wasm), but\n * we can sharply limit the blast radius:\n *\n * 1. Cap pattern length \u2014 practically all legitimate user patterns are\n * under 256 characters. A 4 KB pattern is almost certainly malicious\n * or a copy-paste accident.\n * 2. Reject patterns containing the most obvious super-linear structures.\n * This is a coarse filter (false-positives are likely; we accept that\n * for hostile-input contexts).\n *\n * Callers should additionally bound the *subject* length (e.g. by capping\n * line size before matching).\n */\n\nconst MAX_PATTERN_LEN = 256;\n\n// Heuristics for catastrophic-backtracking constructs. Not exhaustive; bias\n// toward false-positives in tools that accept LLM-generated input.\nconst DANGEROUS_PATTERNS: ReadonlyArray<RegExp> = [\n // (a+)+, (.*)+, etc \u2014 nested quantifier on a group with internal quantifier\n /(\\([^)]*[+*][^)]*\\))[+*]/,\n /(\\(\\?:[^)]*[+*][^)]*\\))[+*]/,\n // Adjacent quantifiers: a++ a*+\n /[+*]{2,}/,\n // Quantifier on alternation with length 2+\n /\\([^|)]+\\|[^)]+\\)[+*][+*]/,\n // Greedy quantifier inside lookahead/lookbehind \u2014 (?!.*a+)\n /[([][^)\\]]*[+*][^)\\]]*[)\\]][^)]*\\?\\??/,\n];\n\nexport interface CompileResult {\n ok: true;\n regex: RegExp;\n}\n\nexport interface CompileFail {\n ok: false;\n reason: string;\n}\n\nexport function compileUserRegex(pattern: string, flags: string): CompileResult | CompileFail {\n if (typeof pattern !== 'string') {\n return { ok: false, reason: 'pattern must be a string' };\n }\n if (pattern.length === 0) {\n return { ok: false, reason: 'pattern is empty' };\n }\n if (pattern.length > MAX_PATTERN_LEN) {\n return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };\n }\n for (const rx of DANGEROUS_PATTERNS) {\n if (rx.test(pattern)) {\n return {\n ok: false,\n reason:\n 'pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers',\n };\n }\n }\n try {\n return { ok: true, regex: new RegExp(pattern, flags) };\n } catch (err) {\n return {\n ok: false,\n reason: err instanceof Error ? err.message : 'invalid regex',\n };\n }\n}\n\n/**\n * Truncate a subject line to a safe length for synchronous regex eval.\n * The cap is conservative; tools that need exact-line matching against very\n * long lines should use ripgrep externally rather than the native walker.\n */\nexport const MAX_SUBJECT_LEN = 64 * 1024;\n\nexport function capSubject(line: string): string {\n return line.length > MAX_SUBJECT_LEN ? line.slice(0, MAX_SUBJECT_LEN) : line;\n}\n", "import { createHash } from 'node:crypto';\nimport * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport * as Core from '@wrongstack/core/utils';\nimport type { Context } from '@wrongstack/core/agent';\n\n/**\n * sha-256 hex of a UTF-8 string. Used by the file tools to record a content\n * hash alongside the mtime in `ctx.recordRead` \u2014 the hash is the authoritative\n * staleness arbiter for `edit` (mtime has a 2 s tolerance window on Windows).\n */\nexport function sha256hex(content: string): string {\n return createHash('sha256').update(content, 'utf8').digest('hex');\n}\n/** Detected package manager for a project directory. */\nexport type PackageManager = 'pnpm' | 'yarn' | 'npm';\n\n/**\n * Detect the project's package manager by inspecting lockfiles in `cwd`.\n * Order: pnpm \u2192 yarn \u2192 npm (default). Missing or unreadable directories fall\n * back to `npm` rather than throwing, so a `safeResolve`-checked cwd that\n * happens to be empty never aborts the tool.\n */\nexport async function detectPackageManager(cwd: string): Promise<PackageManager> {\n const { stat } = await import('node:fs/promises');\n try {\n await stat(`${cwd}/pnpm-lock.yaml`);\n return 'pnpm';\n } catch {\n /* not pnpm */\n }\n try {\n await stat(`${cwd}/yarn.lock`);\n return 'yarn';\n } catch {\n /* not yarn */\n }\n return 'npm';\n}\n\nexport function resolvePath(input: string, ctx: Context): string {\n return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.workingDir ?? ctx.cwd, input);\n}\n\n/**\n * Roots every file tool may always reach, even in restricted mode: the\n * project root and the user-global `~/.wrongstack` directory (config, memory,\n * sessions, skills). `~/.wrongstack` honors the `WRONGSTACK_HOME` override.\n */\nfunction allowedRoots(ctx: Context): string[] {\n return [path.resolve(ctx.projectRoot), path.resolve(Core.wstackGlobalRoot())];\n}\n\n/** True if `target` is `root` itself or nested inside any of `roots`. */\nfunction isInsideAny(target: string, roots: string[]): boolean {\n return roots.some((root) => {\n const rel = path.relative(root, target);\n return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));\n });\n}\n\nexport function ensureInsideRoot(absPath: string, ctx: Context): string {\n const target = path.resolve(absPath);\n // Unrestricted filesystem access: skip the project-root containment check.\n if (ctx.allowOutsideProjectRoot) return target;\n if (isInsideAny(target, allowedRoots(ctx))) return target;\n throw new Error(`Path \"${absPath}\" is outside project root \"${path.resolve(ctx.projectRoot)}\"`);\n}\n\nexport function safeResolve(input: string, ctx: Context): string {\n return ensureInsideRoot(resolvePath(input, ctx), ctx);\n}\n\n/**\n * Defense against in-root\u2192out-of-root symlink escape (CWE-59). `safeResolve`\n * only does a syntactic `../` check, so a symlink that lives *inside* the\n * project root but points outside still passes it. This resolves the path\n * through `fs.realpath` and re-verifies containment against the realpath of\n * the project root (comparing like-for-like, since the root itself may be a\n * symlink \u2014 macOS `/var`\u2192`/private/var`, Windows 8.3 short names). For a path\n * that does not exist yet (e.g. a `write` to a new file) the nearest existing\n * ancestor directory is checked instead. Throws if the real target escapes.\n *\n * Mirrors the per-file guard already used in `replace.ts`/`grep.ts`; applied\n * to single-file `read`/`edit`/`write` it throws (rather than skips) because\n * the caller named exactly one file.\n */\nexport async function assertRealInsideRoot(absPath: string, ctx: Context): Promise<void> {\n // Unrestricted filesystem access: no symlink-escape check to perform.\n if (ctx.allowOutsideProjectRoot) return;\n // Compare like-for-like against the realpath of each always-allowed root\n // (project root + ~/.wrongstack), since a root may itself be a symlink.\n const realRoots = await Promise.all(\n allowedRoots(ctx).map((r) => fsp.realpath(r).catch(() => path.resolve(r))),\n );\n let probe = absPath;\n for (;;) {\n let real: string;\n try {\n real = await fsp.realpath(probe);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n const parent = path.dirname(probe);\n if (parent === probe) return; // reached fs root without escaping\n probe = parent;\n continue;\n }\n throw err;\n }\n if (isInsideAny(real, realRoots)) return;\n throw new Error(\n `Path \"${absPath}\" resolves through a symlink outside project root \"${realRoots[0]}\"`,\n );\n }\n}\n\n/** `safeResolve` + symlink realpath containment check. Async. */\nexport async function safeResolveReal(input: string, ctx: Context): Promise<string> {\n const abs = safeResolve(input, ctx);\n await assertRealInsideRoot(abs, ctx);\n return abs;\n}\n\nexport function truncateMiddle(s: string, max: number): string {\n if (Buffer.byteLength(s, 'utf8') <= max) return s;\n const half = Math.floor(max / 2);\n return (\n s.slice(0, half) +\n `\\n\u2026[truncated ${Buffer.byteLength(s, 'utf8') - max} bytes from middle]\u2026\\n` +\n s.slice(-half)\n );\n}\n\nexport function isBinaryBuffer(buf: Buffer): boolean {\n const len = Math.min(buf.length, 8192);\n for (let i = 0; i < len; i++) {\n if (buf[i] === 0) return true;\n }\n return false;\n}\n\n// \u2500\u2500\u2500 Command-output normalization (token-saving) \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// Raw process output is full of tokens the model gains nothing from: ANSI\n// escapes, carriage-return progress spam, runs of identical warning lines, and\n// huge tails of build noise. These helpers strip that noise before the output\n// reaches the LLM. They are scoped to COMMAND tools (bash/git/exec and the\n// _spawn-stream consumers) \u2014 never applied to structured/code outputs.\n\n/** Unified byte cap for all command tool output fed to the model. */\nexport const COMMAND_OUTPUT_MAX_BYTES = 32_768;\n\n/** Runs of >= this many identical consecutive lines are collapsed. */\nconst REPEAT_RUN_THRESHOLD = 3;\n\n/**\n * Collapse carriage-return overwrites the way a terminal would: `\\r\\n` becomes\n * `\\n`, and a bare `\\r` (progress redraw) keeps only the text after the LAST\n * `\\r` on its physical line. Without this, a single progress bar that redraws\n * 200 times explodes into 200 lines.\n */\nexport function collapseCarriageReturns(text: string): string {\n const lf = text.replace(/\\r\\n/g, '\\n');\n if (!lf.includes('\\r')) return lf;\n return lf\n .split('\\n')\n .map((line) => (line.includes('\\r') ? line.slice(line.lastIndexOf('\\r') + 1) : line))\n .join('\\n');\n}\n\n/**\n * Collapse a run of `minRun`+ identical consecutive lines into the line once\n * plus a marker. Consecutive-only \u2014 it never reorders or dedups non-adjacent\n * lines, so diffs/source stay intact.\n */\nexport function collapseConsecutiveDuplicates(text: string, minRun = REPEAT_RUN_THRESHOLD): string {\n const lines = text.split('\\n');\n const out: string[] = [];\n let i = 0;\n while (i < lines.length) {\n let j = i + 1;\n while (j < lines.length && lines[j] === lines[i]) j++;\n const run = j - i;\n if (run >= minRun) {\n out.push(lines[i]!, `\u2026 \u27E8repeated ${run}\u00D7\u27E9`);\n } else {\n for (let k = i; k < j; k++) out.push(lines[k]!);\n }\n i = j;\n }\n return out.join('\\n');\n}\n\n/** Largest prefix of `s` whose UTF-8 byte length is <= `maxBytes`. */\nfunction takeHeadBytes(s: string, maxBytes: number): string {\n if (maxBytes <= 0) return '';\n /* v8 ignore next -- only caller (truncateHeadTail) passes a budget smaller than s; defensive. */\n if (Buffer.byteLength(s, 'utf8') <= maxBytes) return s;\n let lo = 0;\n let hi = s.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (Buffer.byteLength(s.slice(0, mid), 'utf8') <= maxBytes) lo = mid;\n else hi = mid - 1;\n }\n return s.slice(0, lo);\n}\n\n/** Largest suffix of `s` whose UTF-8 byte length is <= `maxBytes`. */\nfunction takeTailBytes(s: string, maxBytes: number): string {\n if (maxBytes <= 0) return '';\n /* v8 ignore next -- only caller (truncateHeadTail) passes a budget smaller than s; defensive. */\n if (Buffer.byteLength(s, 'utf8') <= maxBytes) return s;\n let lo = 0;\n let hi = s.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (Buffer.byteLength(s.slice(s.length - mid), 'utf8') <= maxBytes) lo = mid;\n else hi = mid - 1;\n }\n return s.slice(s.length - lo);\n}\n\n/**\n * Truncate to `maxBytes` keeping BOTH ends \u2014 the head (what ran / early context)\n * and the tail (errors and summaries usually land last), biased ~45/55 toward\n * the tail. The result never exceeds `maxBytes`.\n */\nexport function truncateHeadTail(s: string, maxBytes: number): string {\n const total = Buffer.byteLength(s, 'utf8');\n if (total <= maxBytes) return s;\n // Reserve a fixed allowance for the marker so the final string can't exceed\n // the cap even though the dropped-byte count's digit width varies.\n const MARKER_RESERVE = 64;\n const avail = Math.max(0, maxBytes - MARKER_RESERVE);\n const headBudget = Math.floor(avail * 0.45);\n const head = takeHeadBytes(s, headBudget);\n const tail = takeTailBytes(s, avail - Buffer.byteLength(head, 'utf8'));\n const kept = Buffer.byteLength(head, 'utf8') + Buffer.byteLength(tail, 'utf8');\n return `${head}\\n\u2026[truncated ${total - kept} bytes]\u2026\\n${tail}`;\n}\n\n/**\n * Full token-saving pipeline for command tool output: strip ANSI \u2192 collapse\n * carriage-return progress \u2192 trim trailing whitespace \u2192 collapse identical\n * consecutive lines \u2192 squeeze blank-line runs \u2192 head+tail truncate to the cap.\n */\nexport function normalizeCommandOutput(\n raw: string,\n opts: { maxBytes?: number | undefined } = {},\n): string {\n if (!raw) return raw;\n let text = Core.stripAnsi(raw);\n text = collapseCarriageReturns(text);\n text = text.replace(/[ \\t]+$/gm, ''); // trailing whitespace per line\n text = collapseConsecutiveDuplicates(text);\n text = text.replace(/\\n{3,}/g, '\\n\\n'); // >=2 blank lines \u2192 1\n return truncateHeadTail(text, opts.maxBytes ?? COMMAND_OUTPUT_MAX_BYTES);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["path", "lstat", "stat", "resolve"]
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-kanban.d.ts","sourceRoot":"","sources":["../src/session-kanban.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAEhE,OAAO,EAKL,KAAK,QAAQ,EACb,KAAK,QAAQ,EACb,KAAK,QAAQ,EACd,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,mBAAmB,EAAc,MAAM,wBAAwB,CAAC;AAC9E,OAAO,EAAsB,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAC3E,OAAO,EAIL,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,UAAU,EAMhB,MAAM,oBAAoB,CAAC;AAK5B,qEAAqE;AACrE,eAAO,MAAM,sBAAsB,EAAE,YAAY,EAKhD,CAAC;AAyEF,sEAAsE;AACtE,wBAAsB,wBAAwB,CAC5C,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CA0C7B;AAmCD,sFAAsF;AACtF,wBAAsB,gCAAgC,CACpD,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,EAAE,CAAC,CAanB;AAED,gFAAgF;AAChF,wBAAsB,+BAA+B,CACnD,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,eAAe,SAAK,GACnB,OAAO,CAAC,MAAM,EAAE,CAAC,CAkBnB;AAgFD,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,SAAS,EAAE,MAAM,GAChB,mBAAmB,CAqBrB;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,SAAS,EAAE,MAAM,GAChB,mBAAmB,CAqCrB;AAQD,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,SAAS,EAAE,MAAM,GAChB,mBAAmB,CAqBrB;AAED,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAO7B;AAED,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAO7B;AAED,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAO7B;AAiDD,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,SAAS,EAAE,MAAM,GAChB,IAAI,CAON;AAED,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,SAAS,EAAE,MAAM,GAChB,IAAI,CAON;AAED,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,SAAS,EAAE,MAAM,GAChB,IAAI,CAON;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,IAAI,
|
|
1
|
+
{"version":3,"file":"session-kanban.d.ts","sourceRoot":"","sources":["../src/session-kanban.ts"],"names":[],"mappings":"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"}
|
package/dist/session-kanban.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/session-kanban.ts
|
|
2
2
|
import { watch } from "node:fs";
|
|
3
3
|
import { basename, dirname } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { getSharedProjectMailbox } from "@wrongstack/core/coordination";
|
|
5
5
|
import {
|
|
6
6
|
loadPlan,
|
|
7
7
|
loadTasks,
|
|
@@ -11,9 +11,9 @@ import {
|
|
|
11
11
|
import { deserializeTaskGraph } from "@wrongstack/core/tasking";
|
|
12
12
|
import { resolveWstackPaths } from "@wrongstack/core/utils";
|
|
13
13
|
import {
|
|
14
|
+
bridgeKanbanSupervisor,
|
|
14
15
|
createBoard,
|
|
15
16
|
getBoard,
|
|
16
|
-
getKanbanDir,
|
|
17
17
|
listBoards,
|
|
18
18
|
removeBoard,
|
|
19
19
|
syncBoardFromTaskGraph,
|
|
@@ -332,7 +332,7 @@ function broadcastTodoUpdate(context, todos) {
|
|
|
332
332
|
const sessionId = context.session?.id ?? "";
|
|
333
333
|
if (!context.agentId || !sessionId) return;
|
|
334
334
|
const projectDir = resolveWstackPaths({ projectRoot: context.projectRoot }).projectDir;
|
|
335
|
-
const mailbox =
|
|
335
|
+
const mailbox = getSharedProjectMailbox(projectDir);
|
|
336
336
|
void mailbox.send({
|
|
337
337
|
from: context.agentId,
|
|
338
338
|
to: "*",
|
|
@@ -413,7 +413,7 @@ function attachSessionKanbanMirror(context) {
|
|
|
413
413
|
let watcher = null;
|
|
414
414
|
let watchedDir = "";
|
|
415
415
|
let timer = null;
|
|
416
|
-
let
|
|
416
|
+
let unsubscribeBoardEvents = null;
|
|
417
417
|
let watchedBoardId = "";
|
|
418
418
|
let boardTimer = null;
|
|
419
419
|
let presenceTimer = null;
|
|
@@ -441,19 +441,19 @@ function attachSessionKanbanMirror(context) {
|
|
|
441
441
|
const id = sessionId();
|
|
442
442
|
const board = id ? await ensureSessionKanbanBoard(context.projectRoot, id) : null;
|
|
443
443
|
if (!board || board.id === watchedBoardId) return;
|
|
444
|
-
|
|
445
|
-
|
|
444
|
+
unsubscribeBoardEvents?.();
|
|
445
|
+
unsubscribeBoardEvents = null;
|
|
446
446
|
watchedBoardId = board.id;
|
|
447
447
|
try {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
if (filename?.toString() !== boardFileName) return;
|
|
448
|
+
unsubscribeBoardEvents = bridgeKanbanSupervisor(
|
|
449
|
+
context.projectRoot,
|
|
450
|
+
(event) => {
|
|
451
|
+
const data = event.data;
|
|
452
|
+
if (data?.boardId !== board.id) return;
|
|
454
453
|
if (boardTimer) clearTimeout(boardTimer);
|
|
455
454
|
boardTimer = setTimeout(() => fireAndForget("refresh-board", refreshBoard()), 60);
|
|
456
|
-
}
|
|
455
|
+
},
|
|
456
|
+
{ autoReconnect: true, reconnectDelayMs: 1e3 }
|
|
457
457
|
);
|
|
458
458
|
const touchPresence = () => touchKanbanPresence(context.projectRoot, board.id, {
|
|
459
459
|
sessionId: id,
|
|
@@ -464,13 +464,8 @@ function attachSessionKanbanMirror(context) {
|
|
|
464
464
|
if (presenceTimer) clearInterval(presenceTimer);
|
|
465
465
|
presenceTimer = setInterval(() => fireAndForget("touch-presence", touchPresence()), 6e4);
|
|
466
466
|
presenceTimer.unref?.();
|
|
467
|
-
boardWatcher.on("error", () => {
|
|
468
|
-
boardWatcher?.close();
|
|
469
|
-
boardWatcher = null;
|
|
470
|
-
watchedBoardId = "";
|
|
471
|
-
});
|
|
472
467
|
} catch {
|
|
473
|
-
|
|
468
|
+
unsubscribeBoardEvents = null;
|
|
474
469
|
watchedBoardId = "";
|
|
475
470
|
}
|
|
476
471
|
};
|
|
@@ -524,7 +519,7 @@ function attachSessionKanbanMirror(context) {
|
|
|
524
519
|
if (boardTimer) clearTimeout(boardTimer);
|
|
525
520
|
if (presenceTimer) clearInterval(presenceTimer);
|
|
526
521
|
watcher?.close();
|
|
527
|
-
|
|
522
|
+
unsubscribeBoardEvents?.();
|
|
528
523
|
bindings.delete(context);
|
|
529
524
|
if (attachedProjectRoot && registeredSessionId) {
|
|
530
525
|
releaseActiveSessionBoard(attachedProjectRoot, registeredSessionId);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/session-kanban.ts"],
|
|
4
|
-
"sourcesContent": ["import { type FSWatcher, watch } from 'node:fs';\nimport { basename, dirname } from 'node:path';\nimport type { Context, TodoItem } from '@wrongstack/core/agent';\nimport { GlobalMailbox } from '@wrongstack/core/coordination';\nimport {\n loadPlan,\n loadTasks,\n mutatePlan,\n mutateTasks,\n type PlanFile,\n type PlanItem,\n type TaskFile,\n} from '@wrongstack/core/storage';\nimport { deserializeTaskGraph } from '@wrongstack/core/tasking';\nimport type { SerializedTaskGraph, TaskStatus } from '@wrongstack/core/types';\nimport { resolveWstackPaths, type TaskItem } from '@wrongstack/core/utils';\nimport {\n createBoard,\n getBoard,\n getKanbanDir,\n type KanbanBoard,\n type KanbanColumn,\n type KanbanTask,\n listBoards,\n removeBoard,\n syncBoardFromTaskGraph,\n touchKanbanPresence,\n updateBoard,\n} from '@wrongstack/kanban';\n\nconst SESSION_BOARD_TAG = 'session-work';\nconst MIRROR_DISABLED_ENV = 'WRONGSTACK_KANBAN_TASK_MIRROR';\n\n/** The canonical workflow shared by WebUI and TUI session boards. */\nexport const SESSION_KANBAN_COLUMNS: KanbanColumn[] = [\n { id: 'todo', title: 'Todo', order: 0, wipLimit: 0, color: '#2563eb' },\n { id: 'in-progress', title: 'Running', order: 1, wipLimit: 1, color: '#d97706' },\n { id: 'review', title: 'Preview', order: 2, wipLimit: 0, color: '#7c3aed' },\n { id: 'done', title: 'Done', order: 3, wipLimit: 0, color: '#16a34a' },\n];\n\nconst boardQueue = new Map<string, Promise<void>>();\nconst boardEnsures = new Map<string, Promise<KanbanBoard>>();\ntype PendingMirror = {\n projectRoot: string;\n sessionId: string;\n graph: SerializedTaskGraph;\n sourceSystem: 'session-todo' | 'session-task' | 'session-plan';\n};\nconst pendingMirrors = new Map<string, PendingMirror>();\nconst activeMirrors = new Set<string>();\nconst bindings = new WeakMap<Context, () => void>();\nconst suppressedTodoMirrors = new WeakSet<Context>();\nconst activeSessionBoards = new Map<string, number>();\n\nfunction boardKey(projectRoot: string, sessionId: string): string {\n return `${projectRoot}\\0${sessionId}`;\n}\n\nfunction mirrorKey(\n projectRoot: string,\n sessionId: string,\n sourceSystem: PendingMirror['sourceSystem'],\n): string {\n return `${boardKey(projectRoot, sessionId)}\\0${sourceSystem}`;\n}\n\nfunction sessionTag(sessionId: string): string {\n return `session:${sessionId}`;\n}\n\nfunction sessionBoardTitle(sessionId: string): string {\n const leaf = sessionId.split(/[\\\\/]/).filter(Boolean).pop() ?? sessionId;\n return `Session ${leaf.slice(0, 12)}`;\n}\n\nfunction sessionBoardTags(sessionId: string): string[] {\n return ['session', SESSION_BOARD_TAG, sessionTag(sessionId)];\n}\n\nfunction sessionIdFromTags(tags: readonly string[] | undefined): string | null {\n const tag = tags?.find((candidate) => candidate.startsWith('session:'));\n return tag?.slice('session:'.length) || null;\n}\n\nfunction isOwnedSessionBoard(tags: readonly string[] | undefined): boolean {\n return Boolean(tags?.includes(SESSION_BOARD_TAG) && sessionIdFromTags(tags));\n}\n\nfunction retainActiveSessionBoard(projectRoot: string, sessionId: string): void {\n const key = boardKey(projectRoot, sessionId);\n activeSessionBoards.set(key, (activeSessionBoards.get(key) ?? 0) + 1);\n}\n\nfunction releaseActiveSessionBoard(projectRoot: string, sessionId: string): void {\n const key = boardKey(projectRoot, sessionId);\n const remaining = (activeSessionBoards.get(key) ?? 0) - 1;\n if (remaining > 0) activeSessionBoards.set(key, remaining);\n else activeSessionBoards.delete(key);\n}\n\nfunction isSessionBoardActive(projectRoot: string, sessionId: string): boolean {\n return (activeSessionBoards.get(boardKey(projectRoot, sessionId)) ?? 0) > 0;\n}\n\nfunction sameColumns(columns: readonly KanbanColumn[]): boolean {\n return (\n columns.length === SESSION_KANBAN_COLUMNS.length &&\n columns.every((column, index) => column.id === SESSION_KANBAN_COLUMNS[index]?.id)\n );\n}\n\n/** Create (or migrate) the single Kanban board owned by a session. */\nexport async function ensureSessionKanbanBoard(\n projectRoot: string | undefined,\n sessionId: string,\n): Promise<KanbanBoard | null> {\n if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === '0') return null;\n const key = boardKey(projectRoot, sessionId);\n const inFlight = boardEnsures.get(key);\n if (inFlight) return inFlight;\n\n const promise = (async () => {\n const summary = (await listBoards(projectRoot)).find((board) =>\n board.tags?.includes(sessionTag(sessionId)),\n );\n let board = summary ? await getBoard(projectRoot, summary.id) : null;\n if (!board) {\n return createBoard(projectRoot, {\n title: sessionBoardTitle(sessionId),\n description: 'Live session work: todos, tasks, and plan items.',\n tags: sessionBoardTags(sessionId),\n columns: SESSION_KANBAN_COLUMNS,\n generatedBy: `session-kanban:${sessionId}`,\n });\n }\n\n // Boards produced by the older task/plan mirrors used the generic five\n // columns. Migrate only session-owned boards; updateBoard reconciles cards\n // from Backlog into Todo while preserving Running/Preview/Done cards.\n if (!sameColumns(board.columns) || !board.tags?.includes(SESSION_BOARD_TAG)) {\n board =\n (await updateBoard(projectRoot, board.id, {\n title: sessionBoardTitle(sessionId),\n description: 'Live session work: todos, tasks, and plan items.',\n tags: [...new Set([...(board.tags ?? []), ...sessionBoardTags(sessionId)])],\n columns: SESSION_KANBAN_COLUMNS,\n })) ?? board;\n }\n return board;\n })();\n\n boardEnsures.set(key, promise);\n try {\n return await promise;\n } finally {\n boardEnsures.delete(key);\n }\n}\n\nfunction enqueueBoardWork<T>(\n projectRoot: string,\n sessionId: string,\n work: () => Promise<T>,\n): Promise<T> {\n const key = boardKey(projectRoot, sessionId);\n const previous = boardQueue.get(key) ?? Promise.resolve();\n const result = previous.catch(() => undefined).then(work);\n const tail = result.then(\n () => undefined,\n () => undefined,\n );\n boardQueue.set(key, tail);\n void tail.then(() => {\n if (boardQueue.get(key) === tail) boardQueue.delete(key);\n });\n return result;\n}\n\nasync function removeEmptySessionBoard(\n projectRoot: string,\n boardId: string,\n sessionId: string,\n): Promise<string | null> {\n return enqueueBoardWork(projectRoot, sessionId, async () => {\n if (isSessionBoardActive(projectRoot, sessionId)) return null;\n const board = await getBoard(projectRoot, boardId);\n if (!board || board.tasks.length > 0 || !isOwnedSessionBoard(board.tags)) return null;\n if (sessionIdFromTags(board.tags) !== sessionId) return null;\n return (await removeBoard(projectRoot, board.id)) ? board.id : null;\n });\n}\n\n/** Remove a particular inactive session's system-owned board when it has no cards. */\nexport async function cleanupSessionKanbanBoardIfEmpty(\n projectRoot: string | undefined,\n sessionId: string,\n): Promise<string[]> {\n if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === '0') return [];\n if (isSessionBoardActive(projectRoot, sessionId)) return [];\n const candidates = (await listBoards(projectRoot)).filter(\n (board) =>\n board.taskCount === 0 &&\n isOwnedSessionBoard(board.tags) &&\n sessionIdFromTags(board.tags) === sessionId,\n );\n const removed = await Promise.all(\n candidates.map((board) => removeEmptySessionBoard(projectRoot, board.id, sessionId)),\n );\n return removed.filter((boardId): boardId is string => Boolean(boardId));\n}\n\n/** Prune stale empty session boards while preserving manual and live boards. */\nexport async function cleanupEmptySessionKanbanBoards(\n projectRoot: string | undefined,\n activeSessionId = '',\n): Promise<string[]> {\n if (!projectRoot || process.env[MIRROR_DISABLED_ENV] === '0') return [];\n const candidates = (await listBoards(projectRoot)).flatMap((board) => {\n const ownerSessionId = sessionIdFromTags(board.tags);\n return board.taskCount === 0 &&\n isOwnedSessionBoard(board.tags) &&\n ownerSessionId &&\n ownerSessionId !== activeSessionId &&\n !isSessionBoardActive(projectRoot, ownerSessionId)\n ? [{ boardId: board.id, sessionId: ownerSessionId }]\n : [];\n });\n const removed = await Promise.all(\n candidates.map(({ boardId, sessionId }) =>\n removeEmptySessionBoard(projectRoot, boardId, sessionId),\n ),\n );\n return removed.filter((boardId): boardId is string => Boolean(boardId));\n}\n\nasync function projectGraph(\n projectRoot: string | undefined,\n sessionId: string,\n graph: SerializedTaskGraph,\n sourceSystem: 'session-todo' | 'session-task' | 'session-plan',\n): Promise<KanbanBoard | null> {\n if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === '0') return null;\n return enqueueBoardWork(projectRoot, sessionId, async () => {\n const board = await ensureSessionKanbanBoard(projectRoot, sessionId);\n if (!board) return null;\n const result = await syncBoardFromTaskGraph(\n projectRoot,\n board.id,\n deserializeTaskGraph(graph),\n {\n sourceSystem,\n tags: [...new Set([...(board.tags ?? []), ...sessionBoardTags(sessionId)])],\n archiveMissingTasks: true,\n includeCompletedTasks: true,\n },\n );\n return result?.board ?? null;\n });\n}\n\n/**\n * Queue an observational mirror without retaining every intermediate state.\n * Todo/task/plan streams are independent, but within each stream only the\n * newest pending graph matters. This bounds a stalled file lock to one active\n * and one pending projection instead of an arbitrarily long Promise chain.\n */\nfunction queueLatestMirror(\n projectRoot: string | undefined,\n sessionId: string,\n graph: SerializedTaskGraph,\n sourceSystem: PendingMirror['sourceSystem'],\n): void {\n if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === '0') return;\n const key = mirrorKey(projectRoot, sessionId, sourceSystem);\n pendingMirrors.set(key, { projectRoot, sessionId, graph, sourceSystem });\n if (activeMirrors.has(key)) return;\n activeMirrors.add(key);\n void (async () => {\n try {\n for (;;) {\n const pending = pendingMirrors.get(key);\n if (!pending) break;\n pendingMirrors.delete(key);\n try {\n await projectGraph(\n pending.projectRoot,\n pending.sessionId,\n pending.graph,\n pending.sourceSystem,\n );\n } catch {\n // Mirrors are observational. A newer pending snapshot, if present,\n // still gets a chance after a transient lock or filesystem failure.\n }\n }\n } finally {\n activeMirrors.delete(key);\n // A mirror can arrive between the last Map read and deleting the active\n // marker. Re-arm once so that snapshot cannot be stranded.\n const pending = pendingMirrors.get(key);\n if (pending) {\n pendingMirrors.delete(key);\n queueLatestMirror(\n pending.projectRoot,\n pending.sessionId,\n pending.graph,\n pending.sourceSystem,\n );\n }\n }\n })();\n}\n\nexport function todoListToSerializedGraph(\n todos: readonly TodoItem[],\n sessionId: string,\n): SerializedTaskGraph {\n const nodes = todos.map((todo, index) => ({\n id: todo.id,\n title: todo.content,\n description: todo.activeForm ?? '',\n type: 'chore' as const,\n priority: 'medium' as const,\n status: todo.status,\n createdAt: index,\n updatedAt: index,\n }));\n return {\n id: `todo:${sessionId}`,\n specId: `todo:${sessionId}`,\n title: 'Session todos',\n nodes,\n edges: [],\n rootNodes: nodes.map((node) => node.id),\n createdAt: 0,\n updatedAt: 0,\n };\n}\n\nexport function taskFileToSerializedGraph(\n tasks: readonly TaskItem[],\n sessionId: string,\n): SerializedTaskGraph {\n const ids = new Set(tasks.map((task) => task.id));\n const nodes = tasks.map((task, index) => ({\n id: task.id,\n title: task.title,\n description: task.description ?? '',\n type: task.type,\n priority: task.priority,\n status: task.status,\n ...(task.assignee ? { assignee: task.assignee } : {}),\n ...(task.estimateHours !== undefined ? { estimateHours: task.estimateHours } : {}),\n createdAt: index,\n updatedAt: index,\n }));\n const edges = tasks.flatMap((task) =>\n (task.dependsOn ?? [])\n .filter((dependency) => ids.has(dependency))\n .map((dependency) => ({\n id: `${dependency}->${task.id}`,\n from: dependency,\n to: task.id,\n type: 'depends_on' as const,\n })),\n );\n const hasIncoming = new Set(edges.map((edge) => edge.to));\n const rootNodes = nodes.filter((node) => !hasIncoming.has(node.id)).map((node) => node.id);\n return {\n // Keep the historical graph id so existing mirrored task cards are reused.\n id: `session:${sessionId}`,\n specId: `session:${sessionId}`,\n title: 'Session tasks',\n nodes,\n edges,\n rootNodes: rootNodes.length ? rootNodes : nodes[0] ? [nodes[0].id] : [],\n createdAt: 0,\n updatedAt: 0,\n };\n}\n\nconst PLAN_STATUS_TO_TASK: Record<PlanItem['status'], TaskStatus> = {\n open: 'pending',\n in_progress: 'in_progress',\n done: 'completed',\n};\n\nexport function planFileToSerializedGraph(\n items: readonly PlanItem[],\n sessionId: string,\n): SerializedTaskGraph {\n const nodes = items.map((item, index) => ({\n id: item.id,\n title: item.title,\n description: item.details ?? '',\n type: 'chore' as const,\n priority: 'medium' as const,\n status: PLAN_STATUS_TO_TASK[item.status],\n createdAt: index,\n updatedAt: index,\n }));\n return {\n id: `plan:${sessionId}`,\n specId: `plan:${sessionId}`,\n title: 'Session plan',\n nodes,\n edges: [],\n rootNodes: nodes.map((node) => node.id),\n createdAt: 0,\n updatedAt: 0,\n };\n}\n\nexport function projectSessionTodosToKanban(\n projectRoot: string | undefined,\n todos: readonly TodoItem[],\n sessionId: string,\n): Promise<KanbanBoard | null> {\n return projectGraph(\n projectRoot,\n sessionId,\n todoListToSerializedGraph(todos, sessionId),\n 'session-todo',\n );\n}\n\nexport function projectSessionTasksToKanban(\n projectRoot: string | undefined,\n tasks: readonly TaskItem[],\n sessionId: string,\n): Promise<KanbanBoard | null> {\n return projectGraph(\n projectRoot,\n sessionId,\n taskFileToSerializedGraph(tasks, sessionId),\n 'session-task',\n );\n}\n\nexport function projectSessionPlanToKanban(\n projectRoot: string | undefined,\n items: readonly PlanItem[],\n sessionId: string,\n): Promise<KanbanBoard | null> {\n return projectGraph(\n projectRoot,\n sessionId,\n planFileToSerializedGraph(items, sessionId),\n 'session-plan',\n );\n}\n\nfunction fireAndForget(context: string, work: Promise<unknown>): void {\n void work.catch((err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n console.warn(JSON.stringify({ level: 'warn', event: 'session-kanban', context, message, timestamp: new Date().toISOString() }));\n });\n}\n\nfunction broadcastTodoUpdate(context: Context, todos: readonly TodoItem[]): void {\n const sessionId = context.session?.id ?? '';\n if (!context.agentId || !sessionId) return;\n const projectDir = resolveWstackPaths({ projectRoot: context.projectRoot }).projectDir;\n const mailbox = new GlobalMailbox(projectDir);\n void mailbox\n .send({\n from: context.agentId,\n to: '*',\n type: 'status',\n subject: `Kanban todo list updated (${todos.length} item${todos.length === 1 ? '' : 's'})`,\n body: JSON.stringify({\n kind: 'kanban.todos.updated',\n sessionId,\n revision: context.state.revision,\n todos,\n }),\n priority: 'normal',\n senderSessionId: sessionId,\n ttlMs: 6 * 60 * 60 * 1000,\n })\n .catch(() => {\n // Mailbox awareness is best-effort; canonical state is already updated.\n });\n}\n\nfunction notifyTodoUpdate(context: Context, todos: readonly TodoItem[]): void {\n const summary = todos.length\n ? todos.map((todo) => `- [${todo.status}] ${todo.content} (${todo.id})`).join('\\n')\n : '- No active todos remain.';\n const text = `[KANBAN TODO UPDATE]\\nAnother Kanban agent reassessed the shared board. The canonical todo list is now:\\n${summary}\\nReassess your current plan before continuing; do not rely on the initial todo snapshot.`;\n const state = context.state as Partial<Context['state']>;\n if (typeof state.appendBlockToLastUserMessage === 'function') {\n if (state.appendBlockToLastUserMessage({ type: 'text', text })) return;\n }\n if (typeof state.appendMessage === 'function') {\n state.appendMessage({ role: 'user', content: [{ type: 'text', text }] });\n }\n}\n\nexport function mirrorSessionTodosToKanban(\n projectRoot: string | undefined,\n todos: readonly TodoItem[],\n sessionId: string,\n): void {\n queueLatestMirror(\n projectRoot,\n sessionId,\n todoListToSerializedGraph(todos, sessionId),\n 'session-todo',\n );\n}\n\nexport function mirrorSessionTasksToKanban(\n projectRoot: string | undefined,\n tasks: readonly TaskItem[],\n sessionId: string,\n): void {\n queueLatestMirror(\n projectRoot,\n sessionId,\n taskFileToSerializedGraph(tasks, sessionId),\n 'session-task',\n );\n}\n\nexport function mirrorSessionPlanToKanban(\n projectRoot: string | undefined,\n items: readonly PlanItem[],\n sessionId: string,\n): void {\n queueLatestMirror(\n projectRoot,\n sessionId,\n planFileToSerializedGraph(items, sessionId),\n 'session-plan',\n );\n}\n\n/**\n * Bind all live session work paths to Kanban. Todo changes are observed from\n * ConversationState; plan/task sidecars are watched so slash commands, WebUI,\n * plugins, and tools all pass through the same board.\n */\nexport function attachSessionKanbanMirror(context: Context): () => void {\n const existing = bindings.get(context);\n if (existing) return existing;\n\n const attachedProjectRoot = context.projectRoot ?? '';\n let registeredSessionId = '';\n const syncActiveSessionRegistration = () => {\n if (!attachedProjectRoot) return;\n const currentSessionId = context.session?.id ?? '';\n if (currentSessionId === registeredSessionId) return;\n if (registeredSessionId) {\n releaseActiveSessionBoard(attachedProjectRoot, registeredSessionId);\n fireAndForget(\n 'cleanup-board',\n cleanupSessionKanbanBoardIfEmpty(attachedProjectRoot, registeredSessionId),\n );\n }\n registeredSessionId = currentSessionId;\n if (registeredSessionId) {\n retainActiveSessionBoard(attachedProjectRoot, registeredSessionId);\n }\n };\n syncActiveSessionRegistration();\n\n let watcher: FSWatcher | null = null;\n let watchedDir = '';\n let timer: NodeJS.Timeout | null = null;\n let boardWatcher: FSWatcher | null = null;\n let watchedBoardId = '';\n let boardTimer: NodeJS.Timeout | null = null;\n let presenceTimer: NodeJS.Timeout | null = null;\n\n const sessionId = () => context.session?.id ?? '';\n const refreshFiles = async () => {\n const id = sessionId();\n if (!id) return;\n const planPath = context.meta['plan.path'];\n if (typeof planPath === 'string' && planPath) {\n const plan = await loadPlan(planPath);\n if (plan) await projectSessionPlanToKanban(context.projectRoot, plan.items, id);\n }\n const taskPath = context.meta['task.path'];\n if (typeof taskPath === 'string' && taskPath) {\n const tasks = await loadTasks(taskPath);\n if (tasks) await projectSessionTasksToKanban(context.projectRoot, tasks.tasks, id);\n }\n };\n\n const refreshBoard = async () => {\n if (!watchedBoardId) return;\n const board = await getBoard(context.projectRoot, watchedBoardId);\n if (board) applySessionKanbanBoardToTodos(context, board);\n };\n\n const configureBoardWatcher = async () => {\n const id = sessionId();\n const board = id ? await ensureSessionKanbanBoard(context.projectRoot, id) : null;\n if (!board || board.id === watchedBoardId) return;\n boardWatcher?.close();\n boardWatcher = null;\n watchedBoardId = board.id;\n try {\n const boardFileName = `${board.id}.json`;\n boardWatcher = watch(\n getKanbanDir(context.projectRoot),\n { persistent: false },\n (_event, filename) => {\n if (filename?.toString() !== boardFileName) return;\n if (boardTimer) clearTimeout(boardTimer);\n boardTimer = setTimeout(() => fireAndForget('refresh-board', refreshBoard()), 60);\n },\n );\n const touchPresence = () =>\n touchKanbanPresence(context.projectRoot, board.id, {\n sessionId: id,\n agentId: context.agentId,\n agentName: context.agentName,\n });\n fireAndForget('touch-presence', touchPresence());\n if (presenceTimer) clearInterval(presenceTimer);\n presenceTimer = setInterval(() => fireAndForget('touch-presence', touchPresence()), 60_000);\n presenceTimer.unref?.();\n boardWatcher.on('error', () => {\n boardWatcher?.close();\n boardWatcher = null;\n watchedBoardId = '';\n });\n } catch {\n boardWatcher = null;\n watchedBoardId = '';\n }\n };\n\n const configureWatcher = () => {\n const planPath = context.meta['plan.path'];\n const taskPath = context.meta['task.path'];\n const candidate =\n typeof planPath === 'string' && planPath\n ? dirname(planPath)\n : typeof taskPath === 'string' && taskPath\n ? dirname(taskPath)\n : '';\n if (!candidate || candidate === watchedDir) return;\n watcher?.close();\n watcher = null;\n watchedDir = candidate;\n try {\n watcher = watch(candidate, { persistent: false }, (_event, filename) => {\n const name = filename?.toString();\n const currentPlanPath = context.meta['plan.path'];\n const currentTaskPath = context.meta['task.path'];\n const planName = typeof currentPlanPath === 'string' ? basename(currentPlanPath) : '';\n const taskName = typeof currentTaskPath === 'string' ? basename(currentTaskPath) : '';\n if (name && name !== planName && name !== taskName) return;\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => fireAndForget('refresh-files', refreshFiles()), 60);\n });\n watcher.on('error', () => watcher?.close());\n } catch {\n // The session directory can briefly disappear during project/session\n // switches; the next meta update re-attempts the binding.\n watcher = null;\n watchedDir = '';\n }\n };\n\n const unsubscribe = context.state.onChange((change) => {\n if (change.kind === 'todos_replaced' && !suppressedTodoMirrors.has(context)) {\n // ConversationState auto-clears an all-done tactical list. Project the\n // pre-clear completion snapshot so every card reaches Done atomically.\n mirrorSessionTodosToKanban(\n context.projectRoot,\n change.completedSnapshot ?? change.todos,\n sessionId(),\n );\n return;\n }\n if (change.kind === 'meta_set' && (change.key === 'plan.path' || change.key === 'task.path')) {\n syncActiveSessionRegistration();\n configureWatcher();\n fireAndForget('ensure-board', ensureSessionKanbanBoard(context.projectRoot, sessionId()));\n fireAndForget('configure-watcher', configureBoardWatcher());\n fireAndForget('refresh-files', refreshFiles());\n }\n });\n\n configureWatcher();\n fireAndForget('configure-watcher', configureBoardWatcher());\n\n const detach = () => {\n unsubscribe();\n if (timer) clearTimeout(timer);\n if (boardTimer) clearTimeout(boardTimer);\n if (presenceTimer) clearInterval(presenceTimer);\n watcher?.close();\n boardWatcher?.close();\n bindings.delete(context);\n if (attachedProjectRoot && registeredSessionId) {\n releaseActiveSessionBoard(attachedProjectRoot, registeredSessionId);\n fireAndForget(\n 'cleanup-board',\n cleanupSessionKanbanBoardIfEmpty(attachedProjectRoot, registeredSessionId),\n );\n registeredSessionId = '';\n }\n };\n bindings.set(context, detach);\n return detach;\n}\n\n/** Fully hydrate a session board before a host announces the session as ready. */\nexport async function hydrateSessionKanban(context: Context): Promise<KanbanBoard | null> {\n const id = context.session?.id ?? '';\n if (!id) return null;\n await cleanupEmptySessionKanbanBoards(context.projectRoot, id);\n let board = await ensureSessionKanbanBoard(context.projectRoot, id);\n if (context.todos.length) {\n board = await projectSessionTodosToKanban(context.projectRoot, context.todos, id);\n }\n const planPath = context.meta['plan.path'];\n if (typeof planPath === 'string' && planPath) {\n const plan = await loadPlan(planPath);\n if (plan) board = await projectSessionPlanToKanban(context.projectRoot, plan.items, id);\n }\n const taskPath = context.meta['task.path'];\n if (typeof taskPath === 'string' && taskPath) {\n const tasks = await loadTasks(taskPath);\n if (tasks) board = await projectSessionTasksToKanban(context.projectRoot, tasks.tasks, id);\n }\n return board;\n}\n\nexport interface SessionKanbanSourceUpdate {\n source: 'todo' | 'task' | 'plan' | null;\n todos?: TodoItem[] | undefined;\n tasks?: TaskFile | undefined;\n plan?: PlanFile | undefined;\n}\n\nfunction sourceStatus(task: KanbanTask): TaskStatus {\n if (task.status === 'completed') return 'completed';\n if (task.status === 'in_progress') return 'in_progress';\n if (task.status === 'review') return 'review';\n if (task.status === 'blocked') return 'blocked';\n if (task.status === 'failed') return 'failed';\n return 'pending';\n}\n\nfunction todoStatus(task: KanbanTask): TodoItem['status'] {\n const status = sourceStatus(task);\n if (status === 'completed') return 'completed';\n if (status === 'in_progress' || status === 'review') return 'in_progress';\n return 'pending';\n}\n\nfunction sessionTodoFromTask(task: KanbanTask): TodoItem {\n return {\n id: task.origin?.taskId ?? task.id,\n content: task.title,\n status: todoStatus(task),\n ...(task.description ? { activeForm: task.description } : {}),\n };\n}\n\nfunction sameTodos(left: readonly TodoItem[], right: readonly TodoItem[]): boolean {\n return (\n left.length === right.length &&\n left.every((todo, index) => {\n const candidate = right[index];\n return (\n candidate?.id === todo.id &&\n candidate.content === todo.content &&\n candidate.status === todo.status &&\n candidate.activeForm === todo.activeForm &&\n candidate.promotedFromPlan === todo.promotedFromPlan &&\n candidate.promotedFromTask === todo.promotedFromTask\n );\n })\n );\n}\n\n/**\n * Replace the tactical todo list from the current cards on a session-owned board.\n *\n * Existing mirrored todo cards retain their stable source ids. New cards created\n * by a Kanban worker become todos under their card ids, so reassessment can add,\n * split, merge, reprioritize, or remove work without being overwritten by the\n * todo list that happened to exist when the run started.\n */\nexport function applySessionKanbanBoardToTodos(context: Context, board: KanbanBoard): TodoItem[] {\n const sessionId = context.session?.id ?? '';\n if (\n !sessionId ||\n sessionIdFromTags(board.tags) !== sessionId ||\n !isOwnedSessionBoard(board.tags)\n ) {\n return [...context.todos];\n }\n\n const projectedTodos = board.tasks\n .filter(\n (task) =>\n task.status !== 'archived' &&\n (!task.origin ||\n task.origin.system === 'session-todo' ||\n (task.origin.graphId ?? '').startsWith('todo:')),\n )\n .sort((left, right) => {\n const leftColumn = board.columns.find((column) => column.id === left.columnId)?.order ?? 0;\n const rightColumn = board.columns.find((column) => column.id === right.columnId)?.order ?? 0;\n return (\n leftColumn - rightColumn ||\n left.order - right.order ||\n left.createdAt.localeCompare(right.createdAt)\n );\n })\n .map(sessionTodoFromTask);\n\n const allCompleted =\n projectedTodos.length > 0 && projectedTodos.every((todo) => todo.status === 'completed');\n const effectiveTodos = allCompleted ? [] : projectedTodos;\n if (sameTodos(context.todos, effectiveTodos)) return [...context.todos];\n suppressedTodoMirrors.add(context);\n try {\n context.state.replaceTodos(projectedTodos);\n } finally {\n suppressedTodoMirrors.delete(context);\n }\n notifyTodoUpdate(context, context.todos);\n broadcastTodoUpdate(context, context.todos);\n return [...context.todos];\n}\n\n/** Reflect a TUI/WebUI Kanban card edit back to its originating work list. */\nexport async function applySessionKanbanTaskToSource(\n context: Context,\n task: KanbanTask,\n options: { remove?: boolean | undefined } = {},\n): Promise<SessionKanbanSourceUpdate> {\n const originId = task.origin?.taskId;\n const graphId = task.origin?.graphId ?? '';\n if (!originId) return { source: null };\n\n if (task.origin?.system === 'session-todo' || graphId.startsWith('todo:')) {\n const next = options.remove\n ? context.todos.filter((todo) => todo.id !== originId)\n : context.todos.map((todo) =>\n todo.id === originId\n ? {\n ...todo,\n content: task.title,\n status:\n sourceStatus(task) === 'completed'\n ? ('completed' as const)\n : sourceStatus(task) === 'in_progress' || sourceStatus(task) === 'review'\n ? ('in_progress' as const)\n : ('pending' as const),\n }\n : todo,\n );\n suppressedTodoMirrors.add(context);\n try {\n context.state.replaceTodos(next);\n } finally {\n suppressedTodoMirrors.delete(context);\n }\n return { source: 'todo', todos: [...context.todos] };\n }\n\n const id = context.session?.id ?? '';\n if (task.origin?.system === 'session-plan' || graphId.startsWith('plan:')) {\n const planPath = context.meta['plan.path'];\n if (typeof planPath !== 'string' || !planPath) return { source: 'plan' };\n const plan = await mutatePlan(planPath, id, (file) => ({\n ...file,\n updatedAt: new Date().toISOString(),\n items: options.remove\n ? file.items.filter((item) => item.id !== originId)\n : file.items.map((item) =>\n item.id === originId\n ? {\n ...item,\n title: task.title,\n details: task.description,\n status:\n task.status === 'completed'\n ? ('done' as const)\n : task.status === 'in_progress' || task.status === 'review'\n ? ('in_progress' as const)\n : ('open' as const),\n updatedAt: new Date().toISOString(),\n }\n : item,\n ),\n }));\n return { source: 'plan', plan };\n }\n\n if (\n task.origin?.system === 'session-task' ||\n task.origin?.system === 'session' ||\n graphId.startsWith('session:')\n ) {\n const taskPath = context.meta['task.path'];\n if (typeof taskPath !== 'string' || !taskPath) return { source: 'task' };\n const tasks = await mutateTasks(taskPath, id, (file) => ({\n ...file,\n tasks: options.remove\n ? file.tasks.filter((item) => item.id !== originId)\n : file.tasks.map((item) =>\n item.id === originId\n ? {\n ...item,\n title: task.title,\n description: task.description,\n status: sourceStatus(task),\n updatedAt: new Date().toISOString(),\n }\n : item,\n ),\n }));\n return { source: 'task', tasks };\n }\n\n return { source: null };\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { type FSWatcher, watch } from 'node:fs';\nimport { basename, dirname } from 'node:path';\nimport type { Context, TodoItem } from '@wrongstack/core/agent';\nimport { getSharedProjectMailbox } from '@wrongstack/core/coordination';\nimport {\n loadPlan,\n loadTasks,\n mutatePlan,\n mutateTasks,\n type PlanFile,\n type PlanItem,\n type TaskFile,\n} from '@wrongstack/core/storage';\nimport { deserializeTaskGraph } from '@wrongstack/core/tasking';\nimport type { SerializedTaskGraph, TaskStatus } from '@wrongstack/core/types';\nimport { resolveWstackPaths, type TaskItem } from '@wrongstack/core/utils';\nimport {\n bridgeKanbanSupervisor,\n createBoard,\n getBoard,\n type KanbanBoard,\n type KanbanColumn,\n type KanbanTask,\n listBoards,\n removeBoard,\n syncBoardFromTaskGraph,\n touchKanbanPresence,\n updateBoard,\n} from '@wrongstack/kanban';\n\nconst SESSION_BOARD_TAG = 'session-work';\nconst MIRROR_DISABLED_ENV = 'WRONGSTACK_KANBAN_TASK_MIRROR';\n\n/** The canonical workflow shared by WebUI and TUI session boards. */\nexport const SESSION_KANBAN_COLUMNS: KanbanColumn[] = [\n { id: 'todo', title: 'Todo', order: 0, wipLimit: 0, color: '#2563eb' },\n { id: 'in-progress', title: 'Running', order: 1, wipLimit: 1, color: '#d97706' },\n { id: 'review', title: 'Preview', order: 2, wipLimit: 0, color: '#7c3aed' },\n { id: 'done', title: 'Done', order: 3, wipLimit: 0, color: '#16a34a' },\n];\n\nconst boardQueue = new Map<string, Promise<void>>();\nconst boardEnsures = new Map<string, Promise<KanbanBoard>>();\ntype PendingMirror = {\n projectRoot: string;\n sessionId: string;\n graph: SerializedTaskGraph;\n sourceSystem: 'session-todo' | 'session-task' | 'session-plan';\n};\nconst pendingMirrors = new Map<string, PendingMirror>();\nconst activeMirrors = new Set<string>();\nconst bindings = new WeakMap<Context, () => void>();\nconst suppressedTodoMirrors = new WeakSet<Context>();\nconst activeSessionBoards = new Map<string, number>();\n\nfunction boardKey(projectRoot: string, sessionId: string): string {\n return `${projectRoot}\\0${sessionId}`;\n}\n\nfunction mirrorKey(\n projectRoot: string,\n sessionId: string,\n sourceSystem: PendingMirror['sourceSystem'],\n): string {\n return `${boardKey(projectRoot, sessionId)}\\0${sourceSystem}`;\n}\n\nfunction sessionTag(sessionId: string): string {\n return `session:${sessionId}`;\n}\n\nfunction sessionBoardTitle(sessionId: string): string {\n const leaf = sessionId.split(/[\\\\/]/).filter(Boolean).pop() ?? sessionId;\n return `Session ${leaf.slice(0, 12)}`;\n}\n\nfunction sessionBoardTags(sessionId: string): string[] {\n return ['session', SESSION_BOARD_TAG, sessionTag(sessionId)];\n}\n\nfunction sessionIdFromTags(tags: readonly string[] | undefined): string | null {\n const tag = tags?.find((candidate) => candidate.startsWith('session:'));\n return tag?.slice('session:'.length) || null;\n}\n\nfunction isOwnedSessionBoard(tags: readonly string[] | undefined): boolean {\n return Boolean(tags?.includes(SESSION_BOARD_TAG) && sessionIdFromTags(tags));\n}\n\nfunction retainActiveSessionBoard(projectRoot: string, sessionId: string): void {\n const key = boardKey(projectRoot, sessionId);\n activeSessionBoards.set(key, (activeSessionBoards.get(key) ?? 0) + 1);\n}\n\nfunction releaseActiveSessionBoard(projectRoot: string, sessionId: string): void {\n const key = boardKey(projectRoot, sessionId);\n const remaining = (activeSessionBoards.get(key) ?? 0) - 1;\n if (remaining > 0) activeSessionBoards.set(key, remaining);\n else activeSessionBoards.delete(key);\n}\n\nfunction isSessionBoardActive(projectRoot: string, sessionId: string): boolean {\n return (activeSessionBoards.get(boardKey(projectRoot, sessionId)) ?? 0) > 0;\n}\n\nfunction sameColumns(columns: readonly KanbanColumn[]): boolean {\n return (\n columns.length === SESSION_KANBAN_COLUMNS.length &&\n columns.every((column, index) => column.id === SESSION_KANBAN_COLUMNS[index]?.id)\n );\n}\n\n/** Create (or migrate) the single Kanban board owned by a session. */\nexport async function ensureSessionKanbanBoard(\n projectRoot: string | undefined,\n sessionId: string,\n): Promise<KanbanBoard | null> {\n if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === '0') return null;\n const key = boardKey(projectRoot, sessionId);\n const inFlight = boardEnsures.get(key);\n if (inFlight) return inFlight;\n\n const promise = (async () => {\n const summary = (await listBoards(projectRoot)).find((board) =>\n board.tags?.includes(sessionTag(sessionId)),\n );\n let board = summary ? await getBoard(projectRoot, summary.id) : null;\n if (!board) {\n return createBoard(projectRoot, {\n title: sessionBoardTitle(sessionId),\n description: 'Live session work: todos, tasks, and plan items.',\n tags: sessionBoardTags(sessionId),\n columns: SESSION_KANBAN_COLUMNS,\n generatedBy: `session-kanban:${sessionId}`,\n });\n }\n\n // Boards produced by the older task/plan mirrors used the generic five\n // columns. Migrate only session-owned boards; updateBoard reconciles cards\n // from Backlog into Todo while preserving Running/Preview/Done cards.\n if (!sameColumns(board.columns) || !board.tags?.includes(SESSION_BOARD_TAG)) {\n board =\n (await updateBoard(projectRoot, board.id, {\n title: sessionBoardTitle(sessionId),\n description: 'Live session work: todos, tasks, and plan items.',\n tags: [...new Set([...(board.tags ?? []), ...sessionBoardTags(sessionId)])],\n columns: SESSION_KANBAN_COLUMNS,\n })) ?? board;\n }\n return board;\n })();\n\n boardEnsures.set(key, promise);\n try {\n return await promise;\n } finally {\n boardEnsures.delete(key);\n }\n}\n\nfunction enqueueBoardWork<T>(\n projectRoot: string,\n sessionId: string,\n work: () => Promise<T>,\n): Promise<T> {\n const key = boardKey(projectRoot, sessionId);\n const previous = boardQueue.get(key) ?? Promise.resolve();\n const result = previous.catch(() => undefined).then(work);\n const tail = result.then(\n () => undefined,\n () => undefined,\n );\n boardQueue.set(key, tail);\n void tail.then(() => {\n if (boardQueue.get(key) === tail) boardQueue.delete(key);\n });\n return result;\n}\n\nasync function removeEmptySessionBoard(\n projectRoot: string,\n boardId: string,\n sessionId: string,\n): Promise<string | null> {\n return enqueueBoardWork(projectRoot, sessionId, async () => {\n if (isSessionBoardActive(projectRoot, sessionId)) return null;\n const board = await getBoard(projectRoot, boardId);\n if (!board || board.tasks.length > 0 || !isOwnedSessionBoard(board.tags)) return null;\n if (sessionIdFromTags(board.tags) !== sessionId) return null;\n return (await removeBoard(projectRoot, board.id)) ? board.id : null;\n });\n}\n\n/** Remove a particular inactive session's system-owned board when it has no cards. */\nexport async function cleanupSessionKanbanBoardIfEmpty(\n projectRoot: string | undefined,\n sessionId: string,\n): Promise<string[]> {\n if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === '0') return [];\n if (isSessionBoardActive(projectRoot, sessionId)) return [];\n const candidates = (await listBoards(projectRoot)).filter(\n (board) =>\n board.taskCount === 0 &&\n isOwnedSessionBoard(board.tags) &&\n sessionIdFromTags(board.tags) === sessionId,\n );\n const removed = await Promise.all(\n candidates.map((board) => removeEmptySessionBoard(projectRoot, board.id, sessionId)),\n );\n return removed.filter((boardId): boardId is string => Boolean(boardId));\n}\n\n/** Prune stale empty session boards while preserving manual and live boards. */\nexport async function cleanupEmptySessionKanbanBoards(\n projectRoot: string | undefined,\n activeSessionId = '',\n): Promise<string[]> {\n if (!projectRoot || process.env[MIRROR_DISABLED_ENV] === '0') return [];\n const candidates = (await listBoards(projectRoot)).flatMap((board) => {\n const ownerSessionId = sessionIdFromTags(board.tags);\n return board.taskCount === 0 &&\n isOwnedSessionBoard(board.tags) &&\n ownerSessionId &&\n ownerSessionId !== activeSessionId &&\n !isSessionBoardActive(projectRoot, ownerSessionId)\n ? [{ boardId: board.id, sessionId: ownerSessionId }]\n : [];\n });\n const removed = await Promise.all(\n candidates.map(({ boardId, sessionId }) =>\n removeEmptySessionBoard(projectRoot, boardId, sessionId),\n ),\n );\n return removed.filter((boardId): boardId is string => Boolean(boardId));\n}\n\nasync function projectGraph(\n projectRoot: string | undefined,\n sessionId: string,\n graph: SerializedTaskGraph,\n sourceSystem: 'session-todo' | 'session-task' | 'session-plan',\n): Promise<KanbanBoard | null> {\n if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === '0') return null;\n return enqueueBoardWork(projectRoot, sessionId, async () => {\n const board = await ensureSessionKanbanBoard(projectRoot, sessionId);\n if (!board) return null;\n const result = await syncBoardFromTaskGraph(\n projectRoot,\n board.id,\n deserializeTaskGraph(graph),\n {\n sourceSystem,\n tags: [...new Set([...(board.tags ?? []), ...sessionBoardTags(sessionId)])],\n archiveMissingTasks: true,\n includeCompletedTasks: true,\n },\n );\n return result?.board ?? null;\n });\n}\n\n/**\n * Queue an observational mirror without retaining every intermediate state.\n * Todo/task/plan streams are independent, but within each stream only the\n * newest pending graph matters. This bounds a stalled file lock to one active\n * and one pending projection instead of an arbitrarily long Promise chain.\n */\nfunction queueLatestMirror(\n projectRoot: string | undefined,\n sessionId: string,\n graph: SerializedTaskGraph,\n sourceSystem: PendingMirror['sourceSystem'],\n): void {\n if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === '0') return;\n const key = mirrorKey(projectRoot, sessionId, sourceSystem);\n pendingMirrors.set(key, { projectRoot, sessionId, graph, sourceSystem });\n if (activeMirrors.has(key)) return;\n activeMirrors.add(key);\n void (async () => {\n try {\n for (;;) {\n const pending = pendingMirrors.get(key);\n if (!pending) break;\n pendingMirrors.delete(key);\n try {\n await projectGraph(\n pending.projectRoot,\n pending.sessionId,\n pending.graph,\n pending.sourceSystem,\n );\n } catch {\n // Mirrors are observational. A newer pending snapshot, if present,\n // still gets a chance after a transient lock or filesystem failure.\n }\n }\n } finally {\n activeMirrors.delete(key);\n // A mirror can arrive between the last Map read and deleting the active\n // marker. Re-arm once so that snapshot cannot be stranded.\n const pending = pendingMirrors.get(key);\n if (pending) {\n pendingMirrors.delete(key);\n queueLatestMirror(\n pending.projectRoot,\n pending.sessionId,\n pending.graph,\n pending.sourceSystem,\n );\n }\n }\n })();\n}\n\nexport function todoListToSerializedGraph(\n todos: readonly TodoItem[],\n sessionId: string,\n): SerializedTaskGraph {\n const nodes = todos.map((todo, index) => ({\n id: todo.id,\n title: todo.content,\n description: todo.activeForm ?? '',\n type: 'chore' as const,\n priority: 'medium' as const,\n status: todo.status,\n createdAt: index,\n updatedAt: index,\n }));\n return {\n id: `todo:${sessionId}`,\n specId: `todo:${sessionId}`,\n title: 'Session todos',\n nodes,\n edges: [],\n rootNodes: nodes.map((node) => node.id),\n createdAt: 0,\n updatedAt: 0,\n };\n}\n\nexport function taskFileToSerializedGraph(\n tasks: readonly TaskItem[],\n sessionId: string,\n): SerializedTaskGraph {\n const ids = new Set(tasks.map((task) => task.id));\n const nodes = tasks.map((task, index) => ({\n id: task.id,\n title: task.title,\n description: task.description ?? '',\n type: task.type,\n priority: task.priority,\n status: task.status,\n ...(task.assignee ? { assignee: task.assignee } : {}),\n ...(task.estimateHours !== undefined ? { estimateHours: task.estimateHours } : {}),\n createdAt: index,\n updatedAt: index,\n }));\n const edges = tasks.flatMap((task) =>\n (task.dependsOn ?? [])\n .filter((dependency) => ids.has(dependency))\n .map((dependency) => ({\n id: `${dependency}->${task.id}`,\n from: dependency,\n to: task.id,\n type: 'depends_on' as const,\n })),\n );\n const hasIncoming = new Set(edges.map((edge) => edge.to));\n const rootNodes = nodes.filter((node) => !hasIncoming.has(node.id)).map((node) => node.id);\n return {\n // Keep the historical graph id so existing mirrored task cards are reused.\n id: `session:${sessionId}`,\n specId: `session:${sessionId}`,\n title: 'Session tasks',\n nodes,\n edges,\n rootNodes: rootNodes.length ? rootNodes : nodes[0] ? [nodes[0].id] : [],\n createdAt: 0,\n updatedAt: 0,\n };\n}\n\nconst PLAN_STATUS_TO_TASK: Record<PlanItem['status'], TaskStatus> = {\n open: 'pending',\n in_progress: 'in_progress',\n done: 'completed',\n};\n\nexport function planFileToSerializedGraph(\n items: readonly PlanItem[],\n sessionId: string,\n): SerializedTaskGraph {\n const nodes = items.map((item, index) => ({\n id: item.id,\n title: item.title,\n description: item.details ?? '',\n type: 'chore' as const,\n priority: 'medium' as const,\n status: PLAN_STATUS_TO_TASK[item.status],\n createdAt: index,\n updatedAt: index,\n }));\n return {\n id: `plan:${sessionId}`,\n specId: `plan:${sessionId}`,\n title: 'Session plan',\n nodes,\n edges: [],\n rootNodes: nodes.map((node) => node.id),\n createdAt: 0,\n updatedAt: 0,\n };\n}\n\nexport function projectSessionTodosToKanban(\n projectRoot: string | undefined,\n todos: readonly TodoItem[],\n sessionId: string,\n): Promise<KanbanBoard | null> {\n return projectGraph(\n projectRoot,\n sessionId,\n todoListToSerializedGraph(todos, sessionId),\n 'session-todo',\n );\n}\n\nexport function projectSessionTasksToKanban(\n projectRoot: string | undefined,\n tasks: readonly TaskItem[],\n sessionId: string,\n): Promise<KanbanBoard | null> {\n return projectGraph(\n projectRoot,\n sessionId,\n taskFileToSerializedGraph(tasks, sessionId),\n 'session-task',\n );\n}\n\nexport function projectSessionPlanToKanban(\n projectRoot: string | undefined,\n items: readonly PlanItem[],\n sessionId: string,\n): Promise<KanbanBoard | null> {\n return projectGraph(\n projectRoot,\n sessionId,\n planFileToSerializedGraph(items, sessionId),\n 'session-plan',\n );\n}\n\nfunction fireAndForget(context: string, work: Promise<unknown>): void {\n void work.catch((err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n console.warn(JSON.stringify({ level: 'warn', event: 'session-kanban', context, message, timestamp: new Date().toISOString() }));\n });\n}\n\nfunction broadcastTodoUpdate(context: Context, todos: readonly TodoItem[]): void {\n const sessionId = context.session?.id ?? '';\n if (!context.agentId || !sessionId) return;\n const projectDir = resolveWstackPaths({ projectRoot: context.projectRoot }).projectDir;\n const mailbox = getSharedProjectMailbox(projectDir);\n void mailbox\n .send({\n from: context.agentId,\n to: '*',\n type: 'status',\n subject: `Kanban todo list updated (${todos.length} item${todos.length === 1 ? '' : 's'})`,\n body: JSON.stringify({\n kind: 'kanban.todos.updated',\n sessionId,\n revision: context.state.revision,\n todos,\n }),\n priority: 'normal',\n senderSessionId: sessionId,\n ttlMs: 6 * 60 * 60 * 1000,\n })\n .catch(() => {\n // Mailbox awareness is best-effort; canonical state is already updated.\n });\n}\n\nfunction notifyTodoUpdate(context: Context, todos: readonly TodoItem[]): void {\n const summary = todos.length\n ? todos.map((todo) => `- [${todo.status}] ${todo.content} (${todo.id})`).join('\\n')\n : '- No active todos remain.';\n const text = `[KANBAN TODO UPDATE]\\nAnother Kanban agent reassessed the shared board. The canonical todo list is now:\\n${summary}\\nReassess your current plan before continuing; do not rely on the initial todo snapshot.`;\n const state = context.state as Partial<Context['state']>;\n if (typeof state.appendBlockToLastUserMessage === 'function') {\n if (state.appendBlockToLastUserMessage({ type: 'text', text })) return;\n }\n if (typeof state.appendMessage === 'function') {\n state.appendMessage({ role: 'user', content: [{ type: 'text', text }] });\n }\n}\n\nexport function mirrorSessionTodosToKanban(\n projectRoot: string | undefined,\n todos: readonly TodoItem[],\n sessionId: string,\n): void {\n queueLatestMirror(\n projectRoot,\n sessionId,\n todoListToSerializedGraph(todos, sessionId),\n 'session-todo',\n );\n}\n\nexport function mirrorSessionTasksToKanban(\n projectRoot: string | undefined,\n tasks: readonly TaskItem[],\n sessionId: string,\n): void {\n queueLatestMirror(\n projectRoot,\n sessionId,\n taskFileToSerializedGraph(tasks, sessionId),\n 'session-task',\n );\n}\n\nexport function mirrorSessionPlanToKanban(\n projectRoot: string | undefined,\n items: readonly PlanItem[],\n sessionId: string,\n): void {\n queueLatestMirror(\n projectRoot,\n sessionId,\n planFileToSerializedGraph(items, sessionId),\n 'session-plan',\n );\n}\n\n/**\n * Bind all live session work paths to Kanban. Todo changes are observed from\n * ConversationState; plan/task sidecars are watched so slash commands, WebUI,\n * plugins, and tools all pass through the same board.\n */\nexport function attachSessionKanbanMirror(context: Context): () => void {\n const existing = bindings.get(context);\n if (existing) return existing;\n\n const attachedProjectRoot = context.projectRoot ?? '';\n let registeredSessionId = '';\n const syncActiveSessionRegistration = () => {\n if (!attachedProjectRoot) return;\n const currentSessionId = context.session?.id ?? '';\n if (currentSessionId === registeredSessionId) return;\n if (registeredSessionId) {\n releaseActiveSessionBoard(attachedProjectRoot, registeredSessionId);\n fireAndForget(\n 'cleanup-board',\n cleanupSessionKanbanBoardIfEmpty(attachedProjectRoot, registeredSessionId),\n );\n }\n registeredSessionId = currentSessionId;\n if (registeredSessionId) {\n retainActiveSessionBoard(attachedProjectRoot, registeredSessionId);\n }\n };\n syncActiveSessionRegistration();\n\n let watcher: FSWatcher | null = null;\n let watchedDir = '';\n let timer: NodeJS.Timeout | null = null;\n let unsubscribeBoardEvents: (() => void) | null = null;\n let watchedBoardId = '';\n let boardTimer: NodeJS.Timeout | null = null;\n let presenceTimer: NodeJS.Timeout | null = null;\n\n const sessionId = () => context.session?.id ?? '';\n const refreshFiles = async () => {\n const id = sessionId();\n if (!id) return;\n const planPath = context.meta['plan.path'];\n if (typeof planPath === 'string' && planPath) {\n const plan = await loadPlan(planPath);\n if (plan) await projectSessionPlanToKanban(context.projectRoot, plan.items, id);\n }\n const taskPath = context.meta['task.path'];\n if (typeof taskPath === 'string' && taskPath) {\n const tasks = await loadTasks(taskPath);\n if (tasks) await projectSessionTasksToKanban(context.projectRoot, tasks.tasks, id);\n }\n };\n\n const refreshBoard = async () => {\n if (!watchedBoardId) return;\n const board = await getBoard(context.projectRoot, watchedBoardId);\n if (board) applySessionKanbanBoardToTodos(context, board);\n };\n\n const configureBoardWatcher = async () => {\n const id = sessionId();\n const board = id ? await ensureSessionKanbanBoard(context.projectRoot, id) : null;\n if (!board || board.id === watchedBoardId) return;\n unsubscribeBoardEvents?.();\n unsubscribeBoardEvents = null;\n watchedBoardId = board.id;\n try {\n unsubscribeBoardEvents = bridgeKanbanSupervisor(\n context.projectRoot,\n (event) => {\n const data = event.data as { boardId?: string } | undefined;\n if (data?.boardId !== board.id) return;\n if (boardTimer) clearTimeout(boardTimer);\n boardTimer = setTimeout(() => fireAndForget('refresh-board', refreshBoard()), 60);\n },\n { autoReconnect: true, reconnectDelayMs: 1_000 },\n );\n const touchPresence = () =>\n touchKanbanPresence(context.projectRoot, board.id, {\n sessionId: id,\n agentId: context.agentId,\n agentName: context.agentName,\n });\n fireAndForget('touch-presence', touchPresence());\n if (presenceTimer) clearInterval(presenceTimer);\n presenceTimer = setInterval(() => fireAndForget('touch-presence', touchPresence()), 60_000);\n presenceTimer.unref?.();\n } catch {\n unsubscribeBoardEvents = null;\n watchedBoardId = '';\n }\n };\n\n const configureWatcher = () => {\n const planPath = context.meta['plan.path'];\n const taskPath = context.meta['task.path'];\n const candidate =\n typeof planPath === 'string' && planPath\n ? dirname(planPath)\n : typeof taskPath === 'string' && taskPath\n ? dirname(taskPath)\n : '';\n if (!candidate || candidate === watchedDir) return;\n watcher?.close();\n watcher = null;\n watchedDir = candidate;\n try {\n watcher = watch(candidate, { persistent: false }, (_event, filename) => {\n const name = filename?.toString();\n const currentPlanPath = context.meta['plan.path'];\n const currentTaskPath = context.meta['task.path'];\n const planName = typeof currentPlanPath === 'string' ? basename(currentPlanPath) : '';\n const taskName = typeof currentTaskPath === 'string' ? basename(currentTaskPath) : '';\n if (name && name !== planName && name !== taskName) return;\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => fireAndForget('refresh-files', refreshFiles()), 60);\n });\n watcher.on('error', () => watcher?.close());\n } catch {\n // The session directory can briefly disappear during project/session\n // switches; the next meta update re-attempts the binding.\n watcher = null;\n watchedDir = '';\n }\n };\n\n const unsubscribe = context.state.onChange((change) => {\n if (change.kind === 'todos_replaced' && !suppressedTodoMirrors.has(context)) {\n // ConversationState auto-clears an all-done tactical list. Project the\n // pre-clear completion snapshot so every card reaches Done atomically.\n mirrorSessionTodosToKanban(\n context.projectRoot,\n change.completedSnapshot ?? change.todos,\n sessionId(),\n );\n return;\n }\n if (change.kind === 'meta_set' && (change.key === 'plan.path' || change.key === 'task.path')) {\n syncActiveSessionRegistration();\n configureWatcher();\n fireAndForget('ensure-board', ensureSessionKanbanBoard(context.projectRoot, sessionId()));\n fireAndForget('configure-watcher', configureBoardWatcher());\n fireAndForget('refresh-files', refreshFiles());\n }\n });\n\n configureWatcher();\n fireAndForget('configure-watcher', configureBoardWatcher());\n\n const detach = () => {\n unsubscribe();\n if (timer) clearTimeout(timer);\n if (boardTimer) clearTimeout(boardTimer);\n if (presenceTimer) clearInterval(presenceTimer);\n watcher?.close();\n unsubscribeBoardEvents?.();\n bindings.delete(context);\n if (attachedProjectRoot && registeredSessionId) {\n releaseActiveSessionBoard(attachedProjectRoot, registeredSessionId);\n fireAndForget(\n 'cleanup-board',\n cleanupSessionKanbanBoardIfEmpty(attachedProjectRoot, registeredSessionId),\n );\n registeredSessionId = '';\n }\n };\n bindings.set(context, detach);\n return detach;\n}\n\n/** Fully hydrate a session board before a host announces the session as ready. */\nexport async function hydrateSessionKanban(context: Context): Promise<KanbanBoard | null> {\n const id = context.session?.id ?? '';\n if (!id) return null;\n await cleanupEmptySessionKanbanBoards(context.projectRoot, id);\n let board = await ensureSessionKanbanBoard(context.projectRoot, id);\n if (context.todos.length) {\n board = await projectSessionTodosToKanban(context.projectRoot, context.todos, id);\n }\n const planPath = context.meta['plan.path'];\n if (typeof planPath === 'string' && planPath) {\n const plan = await loadPlan(planPath);\n if (plan) board = await projectSessionPlanToKanban(context.projectRoot, plan.items, id);\n }\n const taskPath = context.meta['task.path'];\n if (typeof taskPath === 'string' && taskPath) {\n const tasks = await loadTasks(taskPath);\n if (tasks) board = await projectSessionTasksToKanban(context.projectRoot, tasks.tasks, id);\n }\n return board;\n}\n\nexport interface SessionKanbanSourceUpdate {\n source: 'todo' | 'task' | 'plan' | null;\n todos?: TodoItem[] | undefined;\n tasks?: TaskFile | undefined;\n plan?: PlanFile | undefined;\n}\n\nfunction sourceStatus(task: KanbanTask): TaskStatus {\n if (task.status === 'completed') return 'completed';\n if (task.status === 'in_progress') return 'in_progress';\n if (task.status === 'review') return 'review';\n if (task.status === 'blocked') return 'blocked';\n if (task.status === 'failed') return 'failed';\n return 'pending';\n}\n\nfunction todoStatus(task: KanbanTask): TodoItem['status'] {\n const status = sourceStatus(task);\n if (status === 'completed') return 'completed';\n if (status === 'in_progress' || status === 'review') return 'in_progress';\n return 'pending';\n}\n\nfunction sessionTodoFromTask(task: KanbanTask): TodoItem {\n return {\n id: task.origin?.taskId ?? task.id,\n content: task.title,\n status: todoStatus(task),\n ...(task.description ? { activeForm: task.description } : {}),\n };\n}\n\nfunction sameTodos(left: readonly TodoItem[], right: readonly TodoItem[]): boolean {\n return (\n left.length === right.length &&\n left.every((todo, index) => {\n const candidate = right[index];\n return (\n candidate?.id === todo.id &&\n candidate.content === todo.content &&\n candidate.status === todo.status &&\n candidate.activeForm === todo.activeForm &&\n candidate.promotedFromPlan === todo.promotedFromPlan &&\n candidate.promotedFromTask === todo.promotedFromTask\n );\n })\n );\n}\n\n/**\n * Replace the tactical todo list from the current cards on a session-owned board.\n *\n * Existing mirrored todo cards retain their stable source ids. New cards created\n * by a Kanban worker become todos under their card ids, so reassessment can add,\n * split, merge, reprioritize, or remove work without being overwritten by the\n * todo list that happened to exist when the run started.\n */\nexport function applySessionKanbanBoardToTodos(context: Context, board: KanbanBoard): TodoItem[] {\n const sessionId = context.session?.id ?? '';\n if (\n !sessionId ||\n sessionIdFromTags(board.tags) !== sessionId ||\n !isOwnedSessionBoard(board.tags)\n ) {\n return [...context.todos];\n }\n\n const projectedTodos = board.tasks\n .filter(\n (task) =>\n task.status !== 'archived' &&\n (!task.origin ||\n task.origin.system === 'session-todo' ||\n (task.origin.graphId ?? '').startsWith('todo:')),\n )\n .sort((left, right) => {\n const leftColumn = board.columns.find((column) => column.id === left.columnId)?.order ?? 0;\n const rightColumn = board.columns.find((column) => column.id === right.columnId)?.order ?? 0;\n return (\n leftColumn - rightColumn ||\n left.order - right.order ||\n left.createdAt.localeCompare(right.createdAt)\n );\n })\n .map(sessionTodoFromTask);\n\n const allCompleted =\n projectedTodos.length > 0 && projectedTodos.every((todo) => todo.status === 'completed');\n const effectiveTodos = allCompleted ? [] : projectedTodos;\n if (sameTodos(context.todos, effectiveTodos)) return [...context.todos];\n suppressedTodoMirrors.add(context);\n try {\n context.state.replaceTodos(projectedTodos);\n } finally {\n suppressedTodoMirrors.delete(context);\n }\n notifyTodoUpdate(context, context.todos);\n broadcastTodoUpdate(context, context.todos);\n return [...context.todos];\n}\n\n/** Reflect a TUI/WebUI Kanban card edit back to its originating work list. */\nexport async function applySessionKanbanTaskToSource(\n context: Context,\n task: KanbanTask,\n options: { remove?: boolean | undefined } = {},\n): Promise<SessionKanbanSourceUpdate> {\n const originId = task.origin?.taskId;\n const graphId = task.origin?.graphId ?? '';\n if (!originId) return { source: null };\n\n if (task.origin?.system === 'session-todo' || graphId.startsWith('todo:')) {\n const next = options.remove\n ? context.todos.filter((todo) => todo.id !== originId)\n : context.todos.map((todo) =>\n todo.id === originId\n ? {\n ...todo,\n content: task.title,\n status:\n sourceStatus(task) === 'completed'\n ? ('completed' as const)\n : sourceStatus(task) === 'in_progress' || sourceStatus(task) === 'review'\n ? ('in_progress' as const)\n : ('pending' as const),\n }\n : todo,\n );\n suppressedTodoMirrors.add(context);\n try {\n context.state.replaceTodos(next);\n } finally {\n suppressedTodoMirrors.delete(context);\n }\n return { source: 'todo', todos: [...context.todos] };\n }\n\n const id = context.session?.id ?? '';\n if (task.origin?.system === 'session-plan' || graphId.startsWith('plan:')) {\n const planPath = context.meta['plan.path'];\n if (typeof planPath !== 'string' || !planPath) return { source: 'plan' };\n const plan = await mutatePlan(planPath, id, (file) => ({\n ...file,\n updatedAt: new Date().toISOString(),\n items: options.remove\n ? file.items.filter((item) => item.id !== originId)\n : file.items.map((item) =>\n item.id === originId\n ? {\n ...item,\n title: task.title,\n details: task.description,\n status:\n task.status === 'completed'\n ? ('done' as const)\n : task.status === 'in_progress' || task.status === 'review'\n ? ('in_progress' as const)\n : ('open' as const),\n updatedAt: new Date().toISOString(),\n }\n : item,\n ),\n }));\n return { source: 'plan', plan };\n }\n\n if (\n task.origin?.system === 'session-task' ||\n task.origin?.system === 'session' ||\n graphId.startsWith('session:')\n ) {\n const taskPath = context.meta['task.path'];\n if (typeof taskPath !== 'string' || !taskPath) return { source: 'task' };\n const tasks = await mutateTasks(taskPath, id, (file) => ({\n ...file,\n tasks: options.remove\n ? file.tasks.filter((item) => item.id !== originId)\n : file.tasks.map((item) =>\n item.id === originId\n ? {\n ...item,\n title: task.title,\n description: task.description,\n status: sourceStatus(task),\n updatedAt: new Date().toISOString(),\n }\n : item,\n ),\n }));\n return { source: 'task', tasks };\n }\n\n return { source: null };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["board"]
|
|
7
7
|
}
|
package/dist/task.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../src/task.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEzD,OAAO,EAGL,KAAK,QAAQ,EACd,MAAM,wBAAwB,CAAC;AAQhC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAsCnD,UAAU,SAAS;IACjB,yHAAyH;IACzH,MAAM,EAAE,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;IACtE,yCAAyC;IACzC,KAAK,CAAC,EAAE,QAAQ,EAAE,GAAG,SAAS,CAAC;IAC/B,+EAA+E;IAC/E,IAAI,CAAC,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,CAAC,GAAG,SAAS,CAAC;IACpE,8DAA8D;IAC9D,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxB,oCAAoC;IACpC,MAAM,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IAChC,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAChC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CAC/B;AAED,UAAU,UAAU;IAClB,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../src/task.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEzD,OAAO,EAGL,KAAK,QAAQ,EACd,MAAM,wBAAwB,CAAC;AAQhC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAsCnD,UAAU,SAAS;IACjB,yHAAyH;IACzH,MAAM,EAAE,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;IACtE,yCAAyC;IACzC,KAAK,CAAC,EAAE,QAAQ,EAAE,GAAG,SAAS,CAAC;IAC/B,+EAA+E;IAC/E,IAAI,CAAC,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,CAAC,GAAG,SAAS,CAAC;IACpE,8DAA8D;IAC9D,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxB,oCAAoC;IACpC,MAAM,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IAChC,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAChC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CAC/B;AAED,UAAU,UAAU;IAClB,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,UAAU,CAibhD,CAAC"}
|