min-agent 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/README.md +32 -18
  2. package/dist/cli.js +538 -12
  3. package/docs/API.md +25 -8
  4. package/package.json +6 -6
  5. package/skills/self-config/SKILL.md +7 -3
  6. package/skills/self-config/reference.md +8 -4
  7. package/dist/agent.js +0 -1342
  8. package/dist/assistant-stream.js +0 -131
  9. package/dist/cli/commands/chat.js +0 -13
  10. package/dist/cli/commands/exec.js +0 -35
  11. package/dist/cli/commands/history.js +0 -58
  12. package/dist/cli/commands/index.js +0 -241
  13. package/dist/cli/commands/init.js +0 -18
  14. package/dist/cli/commands/mcp.js +0 -173
  15. package/dist/cli/commands/memory.js +0 -87
  16. package/dist/cli/commands/models.js +0 -21
  17. package/dist/cli/commands/permission.js +0 -12
  18. package/dist/cli/commands/rules.js +0 -33
  19. package/dist/cli/commands/sandbox.js +0 -13
  20. package/dist/cli/commands/serve.js +0 -9
  21. package/dist/cli/commands/setup.js +0 -4
  22. package/dist/cli/commands/shared.js +0 -16
  23. package/dist/cli/commands/skills.js +0 -119
  24. package/dist/cli/commands/think.js +0 -12
  25. package/dist/cli/commands/update.js +0 -7
  26. package/dist/cli/commands/write-config.js +0 -52
  27. package/dist/cli/errors.js +0 -36
  28. package/dist/cli/exec-prompt.js +0 -26
  29. package/dist/cli/option-helpers.js +0 -65
  30. package/dist/cli/program.js +0 -217
  31. package/dist/clipboard.js +0 -142
  32. package/dist/code-mode.js +0 -184
  33. package/dist/compaction.js +0 -644
  34. package/dist/config.js +0 -470
  35. package/dist/confirm.js +0 -172
  36. package/dist/context-window.js +0 -246
  37. package/dist/doom-loop.js +0 -278
  38. package/dist/fetch-timeout.js +0 -152
  39. package/dist/http-approvals.js +0 -60
  40. package/dist/http.js +0 -119
  41. package/dist/instructions.js +0 -196
  42. package/dist/logger.js +0 -95
  43. package/dist/markdown.js +0 -259
  44. package/dist/mcp.js +0 -1036
  45. package/dist/memory-cli.js +0 -33
  46. package/dist/memory.js +0 -295
  47. package/dist/model-catalog.js +0 -285
  48. package/dist/output.js +0 -76
  49. package/dist/paste-handler.js +0 -41
  50. package/dist/permission-cli.js +0 -40
  51. package/dist/plugins.js +0 -131
  52. package/dist/pricing.js +0 -119
  53. package/dist/provider.js +0 -63
  54. package/dist/question-format.js +0 -60
  55. package/dist/reasoning-stream.js +0 -158
  56. package/dist/sandbox-cli.js +0 -79
  57. package/dist/sandbox.js +0 -403
  58. package/dist/save-throttle.js +0 -45
  59. package/dist/scope.js +0 -23
  60. package/dist/serve/common.js +0 -425
  61. package/dist/serve/routes-chat.js +0 -367
  62. package/dist/serve/routes-mcp.js +0 -212
  63. package/dist/serve/routes-memory.js +0 -95
  64. package/dist/serve/routes-meta.js +0 -233
  65. package/dist/serve/routes-sessions.js +0 -61
  66. package/dist/serve/routes-skills.js +0 -70
  67. package/dist/serve.js +0 -138
  68. package/dist/sessions.js +0 -265
  69. package/dist/skills.js +0 -599
  70. package/dist/structured-output.js +0 -29
  71. package/dist/synthetic.js +0 -7
  72. package/dist/think-cli.js +0 -36
  73. package/dist/thinking-wire.js +0 -228
  74. package/dist/thinking.js +0 -142
  75. package/dist/title-gen.js +0 -55
  76. package/dist/token-display.js +0 -39
  77. package/dist/tool-display.js +0 -178
  78. package/dist/tool-output.js +0 -126
  79. package/dist/tools/apply_patch.js +0 -265
  80. package/dist/tools/atomic-file.js +0 -35
  81. package/dist/tools/backend.js +0 -61
  82. package/dist/tools/bash.js +0 -231
  83. package/dist/tools/code_search.js +0 -98
  84. package/dist/tools/edit.js +0 -68
  85. package/dist/tools/explore.js +0 -232
  86. package/dist/tools/glob.js +0 -40
  87. package/dist/tools/grep.js +0 -174
  88. package/dist/tools/index.js +0 -34
  89. package/dist/tools/question.js +0 -54
  90. package/dist/tools/read.js +0 -109
  91. package/dist/tools/search-searxng.js +0 -223
  92. package/dist/tools/search-serper.js +0 -189
  93. package/dist/tools/task.js +0 -165
  94. package/dist/tools/todo.js +0 -213
  95. package/dist/tools/web_fetch.js +0 -195
  96. package/dist/tools/web_search.js +0 -228
  97. package/dist/tools/write.js +0 -55
  98. package/dist/tui/App.js +0 -126
  99. package/dist/tui/ConfirmBar.js +0 -66
  100. package/dist/tui/InputBar.js +0 -616
  101. package/dist/tui/MessageList.js +0 -718
  102. package/dist/tui/ModelPicker.js +0 -113
  103. package/dist/tui/QuestionBar.js +0 -136
  104. package/dist/tui/SessionPicker.js +0 -79
  105. package/dist/tui/Spinner.js +0 -6
  106. package/dist/tui/StatusBar.js +0 -22
  107. package/dist/tui/ThinkPicker.js +0 -77
  108. package/dist/tui/agent-runner.js +0 -223
  109. package/dist/tui/bracketed-paste.js +0 -37
  110. package/dist/tui/caret-pos.js +0 -179
  111. package/dist/tui/caret.js +0 -69
  112. package/dist/tui/click-count.js +0 -13
  113. package/dist/tui/diff-view.js +0 -61
  114. package/dist/tui/drag-state.js +0 -49
  115. package/dist/tui/hydrate.js +0 -129
  116. package/dist/tui/index.js +0 -258
  117. package/dist/tui/input-history.js +0 -125
  118. package/dist/tui/layout.js +0 -105
  119. package/dist/tui/mouse.js +0 -46
  120. package/dist/tui/overlay-input.js +0 -12
  121. package/dist/tui/paste-draft.js +0 -173
  122. package/dist/tui/prompt-queue.js +0 -24
  123. package/dist/tui/selection.js +0 -232
  124. package/dist/tui/session-switch.js +0 -28
  125. package/dist/tui/slash-commands.js +0 -123
  126. package/dist/tui/slash-handler.js +0 -589
  127. package/dist/tui/text-width.js +0 -113
  128. package/dist/tui/theme.js +0 -12
  129. package/dist/tui/token-info.js +0 -7
  130. package/dist/tui/tool-children.js +0 -19
  131. package/dist/tui/types.js +0 -1
  132. package/dist/tui/undo-stack.js +0 -14
  133. package/dist/tui/use-sgr-mouse.js +0 -29
  134. package/dist/tui-chat.js +0 -456
  135. package/dist/updater.js +0 -116
  136. package/dist/xml-search.js +0 -194
  137. package/docs/superpowers/plans/2026-08-16-batch1-tui-improvements.md +0 -1510
  138. package/docs/superpowers/plans/2026-08-16-batch2-cli-tools-api.md +0 -2105
  139. package/docs/superpowers/plans/2026-08-16-batch3-config-engineering.md +0 -1595
  140. package/docs/superpowers/plans/2026-08-16-input-caret.md +0 -782
  141. package/docs/superpowers/plans/2026-08-20-tui-completeness.md +0 -873
  142. package/docs/superpowers/plans/2026-08-20-unified-tui-default.md +0 -631
  143. package/docs/superpowers/plans/2026-08-23-input-paste-attachments.md +0 -475
  144. package/docs/superpowers/plans/2026-08-23-thinking-wire-profile.md +0 -450
  145. package/docs/superpowers/specs/2026-08-16-batch1-tui-improvements-design.md +0 -183
  146. package/docs/superpowers/specs/2026-08-16-batch2-cli-tools-api-design.md +0 -220
  147. package/docs/superpowers/specs/2026-08-16-batch3-config-engineering-design.md +0 -196
  148. package/docs/superpowers/specs/2026-08-16-input-caret-design.md +0 -63
  149. package/docs/superpowers/specs/2026-08-17-mouse-selection-design.md +0 -116
  150. package/docs/superpowers/specs/2026-08-20-config-http-alignment-design.md +0 -47
  151. package/docs/superpowers/specs/2026-08-20-mcp-plugins-alignment-design.md +0 -37
  152. package/docs/superpowers/specs/2026-08-20-sandbox-permissions-design.md +0 -68
  153. package/docs/superpowers/specs/2026-08-20-tui-completeness-design.md +0 -273
  154. package/docs/superpowers/specs/2026-08-20-unified-tui-default-design.md +0 -165
  155. package/docs/superpowers/specs/2026-08-23-input-paste-attachments-design.md +0 -174
  156. package/docs/superpowers/specs/2026-08-23-thinking-wire-profile-design.md +0 -140
@@ -1,60 +0,0 @@
1
- export const APPROVAL_TIMEOUT_MS = 300_000;
2
- export function createApprovalGate(timeoutMs = APPROVAL_TIMEOUT_MS) {
3
- const pending = new Map();
4
- const clear = (id) => {
5
- const item = pending.get(id);
6
- if (!item)
7
- return undefined;
8
- clearTimeout(item.timer);
9
- pending.delete(id);
10
- return item;
11
- };
12
- return {
13
- waitConfirm(id) {
14
- return new Promise((resolve) => {
15
- const timer = setTimeout(() => {
16
- pending.delete(id);
17
- resolve(false);
18
- }, timeoutMs);
19
- pending.set(id, { kind: "confirm", resolve, timer });
20
- });
21
- },
22
- waitQuestion(id) {
23
- return new Promise((resolve) => {
24
- const timer = setTimeout(() => {
25
- pending.delete(id);
26
- resolve(null);
27
- }, timeoutMs);
28
- pending.set(id, { kind: "question", resolve, timer });
29
- });
30
- },
31
- resolveConfirm(id, accepted) {
32
- const item = pending.get(id);
33
- if (!item || item.kind !== "confirm")
34
- return false;
35
- clear(id);
36
- item.resolve(accepted);
37
- return true;
38
- },
39
- resolveQuestion(id, answer) {
40
- const item = pending.get(id);
41
- if (!item || item.kind !== "question")
42
- return false;
43
- clear(id);
44
- item.resolve(answer);
45
- return true;
46
- },
47
- cancel(id) {
48
- const item = clear(id);
49
- if (!item)
50
- return;
51
- if (item.kind === "confirm")
52
- item.resolve(false);
53
- else
54
- item.resolve(null);
55
- },
56
- has(id) {
57
- return pending.has(id);
58
- },
59
- };
60
- }
package/dist/http.js DELETED
@@ -1,119 +0,0 @@
1
- import { lookup } from "dns/promises";
2
- import { isIP } from "net";
3
- function isPrivateIpv4(parts) {
4
- if (parts.length !== 4 || parts.some((p) => Number.isNaN(p)))
5
- return true;
6
- const [a, b] = parts;
7
- if (a === 0 || a === 10 || a === 127)
8
- return true;
9
- if (a === 169 && b === 254)
10
- return true;
11
- if (a === 172 && b >= 16 && b <= 31)
12
- return true;
13
- if (a === 192 && b === 168)
14
- return true;
15
- if (a === 100 && b >= 64 && b <= 127)
16
- return true;
17
- if (a === 198 && (b === 18 || b === 19))
18
- return true;
19
- return false;
20
- }
21
- /**
22
- * Decode the tail of an IPv4-mapped IPv6 address (`::ffff:7f00:1` or
23
- * `::ffff:127.0.0.1`) into IPv4 octets, or return null.
24
- */
25
- function mappedIpv4Bytes(ip) {
26
- const tail = ip.slice(7);
27
- if (tail.includes(".")) {
28
- return tail.split(".").map(Number);
29
- }
30
- const groups = tail.split(":");
31
- if (groups.length !== 2)
32
- return null;
33
- const hi = parseInt(groups[0], 16);
34
- const lo = parseInt(groups[1], 16);
35
- if (Number.isNaN(hi) || Number.isNaN(lo))
36
- return null;
37
- return [hi >> 8, hi & 0xff, lo >> 8, lo & 0xff];
38
- }
39
- /** Check whether an IP address belongs to a private / reserved / loopback range. */
40
- export function isPrivateAddress(ip) {
41
- if (ip.startsWith("::ffff:")) {
42
- const bytes = mappedIpv4Bytes(ip);
43
- if (bytes)
44
- return isPrivateIpv4(bytes);
45
- return true;
46
- }
47
- if (isIP(ip) === 6) {
48
- const lower = ip.toLowerCase();
49
- if (lower === "::1" || lower.startsWith("::"))
50
- return true;
51
- if (lower.startsWith("fc") || lower.startsWith("fd"))
52
- return true;
53
- if (lower.startsWith("fe8") || lower.startsWith("fe9") || lower.startsWith("fea") || lower.startsWith("feb"))
54
- return true;
55
- if (lower.startsWith("2001:db8"))
56
- return true;
57
- return false;
58
- }
59
- return isPrivateIpv4(ip.split(".").map(Number));
60
- }
61
- /** Returns an error message if the URL is unsafe to fetch (SSRF protection), or null if safe. */
62
- export async function assertSafeUrl(raw) {
63
- let parsed;
64
- try {
65
- parsed = new URL(raw);
66
- }
67
- catch {
68
- return "Invalid URL";
69
- }
70
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
71
- return "Only http/https URLs are allowed";
72
- }
73
- const host = parsed.hostname;
74
- if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || host.endsWith(".internal")) {
75
- return `Blocked: local hostname "${host}"`;
76
- }
77
- if (isIP(host)) {
78
- return isPrivateAddress(host) ? `Blocked: private/internal IP "${host}"` : null;
79
- }
80
- try {
81
- const addresses = await lookup(host, { all: true });
82
- for (const addr of addresses) {
83
- if (isPrivateAddress(addr.address)) {
84
- return `Blocked: "${host}" resolves to private address "${addr.address}"`;
85
- }
86
- }
87
- }
88
- catch {
89
- // DNS failure — let fetch surface the real error
90
- }
91
- return null;
92
- }
93
- /**
94
- * Read a response body up to maxBytes. Bytes beyond the limit are dropped and
95
- * the read is cancelled early so huge responses never fill memory.
96
- */
97
- export async function readBodyLimited(response, maxBytes) {
98
- if (!response.body) {
99
- return { text: "", truncated: false };
100
- }
101
- const reader = response.body.getReader();
102
- const chunks = [];
103
- let total = 0;
104
- let truncated = false;
105
- for (;;) {
106
- const { done, value } = await reader.read();
107
- if (done)
108
- break;
109
- total += value.byteLength;
110
- if (total > maxBytes) {
111
- chunks.push(Buffer.from(value.subarray(0, value.byteLength - (total - maxBytes))));
112
- truncated = true;
113
- await reader.cancel();
114
- break;
115
- }
116
- chunks.push(Buffer.from(value));
117
- }
118
- return { text: Buffer.concat(chunks).toString("utf-8"), truncated };
119
- }
@@ -1,196 +0,0 @@
1
- import { AsyncLocalStorage } from "async_hooks";
2
- import { readFileSync, existsSync } from "fs";
3
- import path from "path";
4
- import os from "os";
5
- import { getRulesFile, loadConfig } from "./config.js";
6
- /**
7
- * Instruction/rules system.
8
- *
9
- * All config lives under ~/.min-agent/:
10
- * ~/.min-agent/rules.md — Global rules (always loaded)
11
- * ~/.min-agent/config.json — Can specify extra "instructions" paths/URLs
12
- *
13
- * Project-level rules (auto-discovered from cwd):
14
- * ./AGENTS.md, ./RULES.md, ./.min-agent/AGENTS.md
15
- *
16
- * Context-aware (like opencode):
17
- * When the agent reads a file, nearby AGENTS.md/RULES.md are auto-loaded.
18
- */
19
- const PROJECT_FILES = ["AGENTS.md", "RULES.md", "CLAUDE.md"];
20
- function isWithinDir(root, p) {
21
- const rel = path.relative(root, p);
22
- return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
23
- }
24
- function walkUpDirs(startDir, minDir) {
25
- const dirs = [];
26
- let current = path.resolve(startDir);
27
- while (current !== path.dirname(minDir) && isWithinDir(minDir, current)) {
28
- dirs.push(current);
29
- if (current === minDir)
30
- break;
31
- current = path.dirname(current);
32
- }
33
- return dirs;
34
- }
35
- /** Tracks which instruction files have already been loaded to avoid duplicates */
36
- export class InstructionTracker {
37
- loaded = new Set();
38
- isLoaded(filepath) {
39
- return this.loaded.has(path.resolve(filepath));
40
- }
41
- markLoaded(filepath) {
42
- this.loaded.add(path.resolve(filepath));
43
- }
44
- reset() {
45
- this.loaded.clear();
46
- }
47
- /**
48
- * Context-aware: when a file is read, walk up from its directory
49
- * looking for AGENTS.md/RULES.md that haven't been loaded yet.
50
- * Returns new instruction content to inject.
51
- */
52
- resolveForFile(filepath) {
53
- const results = [];
54
- const root = process.cwd();
55
- for (const dir of walkUpDirs(path.dirname(path.resolve(filepath)), root)) {
56
- for (const file of PROJECT_FILES) {
57
- const candidate = path.join(dir, file);
58
- if (existsSync(candidate) && !this.isLoaded(candidate)) {
59
- try {
60
- const content = readFileSync(candidate, "utf-8").trim();
61
- if (content) {
62
- this.markLoaded(candidate);
63
- results.push(`Instructions from: ${candidate}\n${content}`);
64
- }
65
- }
66
- catch { }
67
- }
68
- }
69
- if (results.length > 0)
70
- break;
71
- }
72
- return results;
73
- }
74
- }
75
- const instructionStore = new AsyncLocalStorage();
76
- const fallbackInstructionTracker = new InstructionTracker();
77
- /** Per-run tracker so concurrent HTTP chats do not share loaded-file state. */
78
- export async function runWithInstructionTracker(fn) {
79
- return instructionStore.run(new InstructionTracker(), fn);
80
- }
81
- export function getActiveInstructionTracker() {
82
- return instructionStore.getStore() ?? fallbackInstructionTracker;
83
- }
84
- /** After compaction, nearby AGENTS.md must be allowed to re-inject. */
85
- export function resetActiveInstructionTracker() {
86
- const current = instructionStore.getStore();
87
- if (current)
88
- current.reset();
89
- else
90
- fallbackInstructionTracker.reset();
91
- }
92
- export function findProjectInstructions() {
93
- const results = [];
94
- const root = path.parse(process.cwd()).root;
95
- for (const dir of walkUpDirs(process.cwd(), root)) {
96
- const dotDir = path.join(dir, ".min-agent", "AGENTS.md");
97
- if (existsSync(dotDir)) {
98
- results.push(dotDir);
99
- break;
100
- }
101
- const found = PROJECT_FILES.find((file) => existsSync(path.join(dir, file)));
102
- if (found) {
103
- results.push(path.join(dir, found));
104
- break;
105
- }
106
- }
107
- return results;
108
- }
109
- function findGlobalRules() {
110
- const results = [];
111
- const rulesFile = getRulesFile();
112
- if (existsSync(rulesFile))
113
- results.push(rulesFile);
114
- return results;
115
- }
116
- function resolveConfigInstructions() {
117
- const config = loadConfig();
118
- const instructions = config.instructions ?? [];
119
- const results = [];
120
- for (const item of instructions) {
121
- if (item.startsWith("http://") || item.startsWith("https://"))
122
- continue;
123
- const resolved = item.startsWith("~/")
124
- ? path.join(os.homedir(), item.slice(2))
125
- : path.isAbsolute(item)
126
- ? item
127
- : path.resolve(process.cwd(), item);
128
- if (existsSync(resolved))
129
- results.push(resolved);
130
- }
131
- return results;
132
- }
133
- const REMOTE_CACHE_TTL_MS = 60_000;
134
- const remoteCache = new Map();
135
- async function fetchRemoteInstruction(url) {
136
- const cached = remoteCache.get(url);
137
- if (cached && Date.now() - cached.fetchedAt < REMOTE_CACHE_TTL_MS)
138
- return cached.text;
139
- try {
140
- const response = await fetch(url, { signal: AbortSignal.timeout(5000) });
141
- if (response.ok) {
142
- const text = await response.text();
143
- if (text.trim()) {
144
- remoteCache.set(url, { fetchedAt: Date.now(), text });
145
- return text;
146
- }
147
- }
148
- }
149
- catch { }
150
- return null;
151
- }
152
- export async function fetchRemoteInstructions(urls) {
153
- const config = loadConfig();
154
- const instructions = urls ?? config.instructions ?? [];
155
- const results = await Promise.all(instructions
156
- .filter((i) => i.startsWith("http://") || i.startsWith("https://"))
157
- .map(async (url) => {
158
- const text = await fetchRemoteInstruction(url);
159
- return text ? `Instructions from: ${url}\n${text}` : "";
160
- }));
161
- return results.filter(Boolean);
162
- }
163
- export async function loadInstructions() {
164
- const parts = [];
165
- // Global rules from ~/.min-agent/rules.md
166
- for (const filepath of findGlobalRules()) {
167
- try {
168
- const content = readFileSync(filepath, "utf-8").trim();
169
- if (content)
170
- parts.push(`Instructions from: ${filepath}\n${content}`);
171
- }
172
- catch { }
173
- }
174
- // Project-level instructions
175
- for (const filepath of findProjectInstructions()) {
176
- try {
177
- const content = readFileSync(filepath, "utf-8").trim();
178
- if (content)
179
- parts.push(`Instructions from: ${filepath}\n${content}`);
180
- }
181
- catch { }
182
- }
183
- // Config-defined file instructions
184
- for (const filepath of resolveConfigInstructions()) {
185
- try {
186
- const content = readFileSync(filepath, "utf-8").trim();
187
- if (content)
188
- parts.push(`Instructions from: ${filepath}\n${content}`);
189
- }
190
- catch { }
191
- }
192
- // Remote URL instructions
193
- const remote = await fetchRemoteInstructions();
194
- parts.push(...remote);
195
- return parts;
196
- }
package/dist/logger.js DELETED
@@ -1,95 +0,0 @@
1
- import { mkdirSync, readdirSync, unlinkSync, writeFileSync } from "fs";
2
- import path from "path";
3
- import { getConfigDir } from "./config.js";
4
- const KEEP_DAYS = 7;
5
- function logsDir() {
6
- return path.join(getConfigDir(), "logs");
7
- }
8
- function logFilePath(date) {
9
- const y = date.getFullYear();
10
- const m = String(date.getMonth() + 1).padStart(2, "0");
11
- const d = String(date.getDate()).padStart(2, "0");
12
- return path.join(logsDir(), `min-agent.${y}-${m}-${d}.log`);
13
- }
14
- export function initLogger() {
15
- try {
16
- mkdirSync(logsDir(), { recursive: true });
17
- const cutoff = Date.now() - KEEP_DAYS * 24 * 60 * 60 * 1000;
18
- for (const f of readdirSync(logsDir())) {
19
- const m = f.match(/^min-agent\.(\d{4})-(\d{2})-(\d{2})\.log$/);
20
- if (!m)
21
- continue;
22
- const ts = new Date(`${m[1]}-${m[2]}-${m[3]}T00:00:00`).getTime();
23
- if (ts < cutoff) {
24
- try {
25
- unlinkSync(path.join(logsDir(), f));
26
- }
27
- catch { }
28
- }
29
- }
30
- }
31
- catch { }
32
- }
33
- function writeLog(level, msg) {
34
- try {
35
- const now = new Date();
36
- const time = `${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}:${String(now.getSeconds()).padStart(2, "0")}`;
37
- writeFileSync(logFilePath(now), `[${time}] [${level}]${runTag()} ${scrubSecrets(msg)}\n`, { flag: "a" });
38
- }
39
- catch { }
40
- }
41
- /**
42
- * Run context stamped onto every log line, so interleaved turns (and the passes
43
- * inside one turn) can be told apart when reading the file afterwards.
44
- */
45
- let runContext = null;
46
- function runTag() {
47
- if (!runContext)
48
- return "";
49
- return ` [run=${runContext.runId} pass=${runContext.pass}]`;
50
- }
51
- /** Start a new run scope and return its id. */
52
- export function startRunLog() {
53
- const runId = Math.random().toString(36).slice(2, 8);
54
- runContext = { runId, pass: 0 };
55
- return runId;
56
- }
57
- /** Mark the beginning of a model pass inside the current run. */
58
- export function nextRunPass() {
59
- if (!runContext)
60
- startRunLog();
61
- runContext.pass++;
62
- return runContext.pass;
63
- }
64
- export function endRunLog() {
65
- runContext = null;
66
- }
67
- export function currentRunId() {
68
- return runContext?.runId ?? null;
69
- }
70
- const SECRET_PATTERNS = [
71
- /\b(Bearer\s+)[A-Za-z0-9._~+/-]{8,}/g,
72
- /\b(api[_-]?key|apikey|token|secret|password|passwd)\s*[:=]\s*["']?[A-Za-z0-9._~+/-]{6,}["']?/gi,
73
- /\b(sk-[A-Za-z0-9_-]{6,})/g,
74
- /\b(gh[pousr]_[A-Za-z0-9]{20,})/g,
75
- ];
76
- /** Mask obvious secrets before anything hits the log file. */
77
- function scrubSecrets(msg) {
78
- let out = msg;
79
- for (const re of SECRET_PATTERNS)
80
- out = out.replace(re, "$1***");
81
- return out;
82
- }
83
- export function log(level, msg) {
84
- writeLog(level, msg);
85
- }
86
- function summarize(v, max = 200) {
87
- const s = typeof v === "string" ? v : (JSON.stringify(v) ?? String(v));
88
- return s.length > max ? s.slice(0, max) + "…" : s;
89
- }
90
- export function logToolCall(name, input) {
91
- writeLog("info", `tool_call ${name} ${summarize(input)}`);
92
- }
93
- export function logToolResult(name, output) {
94
- writeLog("info", `tool_result ${name} ${summarize(output)}`);
95
- }