@apolloyh/apollo-agent 0.1.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 (103) hide show
  1. package/.apollo/config.example.json +37 -0
  2. package/.env.example +17 -0
  3. package/README.md +62 -0
  4. package/dist/brand.d.ts +17 -0
  5. package/dist/brand.d.ts.map +1 -0
  6. package/dist/brand.js +23 -0
  7. package/dist/cli-args.d.ts +13 -0
  8. package/dist/cli-args.d.ts.map +1 -0
  9. package/dist/cli-args.js +50 -0
  10. package/dist/config.d.ts +13 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +190 -0
  13. package/dist/constants/spinner-verbs.d.ts +7 -0
  14. package/dist/constants/spinner-verbs.d.ts.map +1 -0
  15. package/dist/constants/spinner-verbs.js +21 -0
  16. package/dist/coordinator/workflow.d.ts +20 -0
  17. package/dist/coordinator/workflow.d.ts.map +1 -0
  18. package/dist/coordinator/workflow.js +120 -0
  19. package/dist/goal/goal-mode.d.ts +26 -0
  20. package/dist/goal/goal-mode.d.ts.map +1 -0
  21. package/dist/goal/goal-mode.js +141 -0
  22. package/dist/hooks/runner.d.ts +43 -0
  23. package/dist/hooks/runner.d.ts.map +1 -0
  24. package/dist/hooks/runner.js +94 -0
  25. package/dist/index.d.ts +3 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +1716 -0
  28. package/dist/llm/anthropic.d.ts +46 -0
  29. package/dist/llm/anthropic.d.ts.map +1 -0
  30. package/dist/llm/anthropic.js +278 -0
  31. package/dist/mcp/manager.d.ts +14 -0
  32. package/dist/mcp/manager.d.ts.map +1 -0
  33. package/dist/mcp/manager.js +93 -0
  34. package/dist/memory/memdir.d.ts +19 -0
  35. package/dist/memory/memdir.d.ts.map +1 -0
  36. package/dist/memory/memdir.js +96 -0
  37. package/dist/plan/plan-mode.d.ts +14 -0
  38. package/dist/plan/plan-mode.d.ts.map +1 -0
  39. package/dist/plan/plan-mode.js +90 -0
  40. package/dist/project-instructions.d.ts +8 -0
  41. package/dist/project-instructions.d.ts.map +1 -0
  42. package/dist/project-instructions.js +33 -0
  43. package/dist/runtime/compaction.d.ts +5 -0
  44. package/dist/runtime/compaction.d.ts.map +1 -0
  45. package/dist/runtime/compaction.js +37 -0
  46. package/dist/runtime/context.d.ts +25 -0
  47. package/dist/runtime/context.d.ts.map +1 -0
  48. package/dist/runtime/context.js +75 -0
  49. package/dist/runtime/permissions.d.ts +11 -0
  50. package/dist/runtime/permissions.d.ts.map +1 -0
  51. package/dist/runtime/permissions.js +119 -0
  52. package/dist/runtime/prompt-builder.d.ts +27 -0
  53. package/dist/runtime/prompt-builder.d.ts.map +1 -0
  54. package/dist/runtime/prompt-builder.js +112 -0
  55. package/dist/runtime/query-engine.d.ts +136 -0
  56. package/dist/runtime/query-engine.d.ts.map +1 -0
  57. package/dist/runtime/query-engine.js +1182 -0
  58. package/dist/runtime/tool-policy.d.ts +13 -0
  59. package/dist/runtime/tool-policy.d.ts.map +1 -0
  60. package/dist/runtime/tool-policy.js +106 -0
  61. package/dist/sdk.d.ts +33 -0
  62. package/dist/sdk.d.ts.map +1 -0
  63. package/dist/sdk.js +41 -0
  64. package/dist/session/resume-ui.d.ts +17 -0
  65. package/dist/session/resume-ui.d.ts.map +1 -0
  66. package/dist/session/resume-ui.js +64 -0
  67. package/dist/session/store.d.ts +66 -0
  68. package/dist/session/store.d.ts.map +1 -0
  69. package/dist/session/store.js +120 -0
  70. package/dist/skillify/skillify.d.ts +12 -0
  71. package/dist/skillify/skillify.d.ts.map +1 -0
  72. package/dist/skillify/skillify.js +52 -0
  73. package/dist/skills/skills.d.ts +26 -0
  74. package/dist/skills/skills.d.ts.map +1 -0
  75. package/dist/skills/skills.js +195 -0
  76. package/dist/status-ui.d.ts +37 -0
  77. package/dist/status-ui.d.ts.map +1 -0
  78. package/dist/status-ui.js +130 -0
  79. package/dist/thought-fold.d.ts +48 -0
  80. package/dist/thought-fold.d.ts.map +1 -0
  81. package/dist/thought-fold.js +156 -0
  82. package/dist/tools/builtin.d.ts +27 -0
  83. package/dist/tools/builtin.d.ts.map +1 -0
  84. package/dist/tools/builtin.js +898 -0
  85. package/dist/tools/registry.d.ts +17 -0
  86. package/dist/tools/registry.d.ts.map +1 -0
  87. package/dist/tools/registry.js +86 -0
  88. package/dist/trace.d.ts +68 -0
  89. package/dist/trace.d.ts.map +1 -0
  90. package/dist/trace.js +600 -0
  91. package/dist/types.d.ts +202 -0
  92. package/dist/types.d.ts.map +1 -0
  93. package/dist/types.js +1 -0
  94. package/dist/utils.d.ts +8 -0
  95. package/dist/utils.d.ts.map +1 -0
  96. package/dist/utils.js +78 -0
  97. package/docs/sdk.md +259 -0
  98. package/package.json +53 -0
  99. package/src/skills/tech-research-skill/SKILL.md +200 -0
  100. package/src/skills/tech-research-skill/agents/openai.yaml +4 -0
  101. package/src/skills/tech-research-skill/repo-analyze +435 -0
  102. package/src/skills/tech-research-skill/repo-fetch +310 -0
  103. package/src/skills/tech-research-skill/report-generate +254 -0
@@ -0,0 +1,195 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { safeRealResolve, truncate } from "../utils.js";
5
+ export class SkillManager {
6
+ config;
7
+ catalogCache;
8
+ contentCache = new Map();
9
+ constructor(config) {
10
+ this.config = config;
11
+ }
12
+ skillDirs() {
13
+ const bundled = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../src/skills");
14
+ return Array.from(new Set([
15
+ ...this.config.skills.directories.map((dir) => path.resolve(this.config.workspaceRoot, dir)),
16
+ bundled,
17
+ ]));
18
+ }
19
+ async search(query) {
20
+ const results = [];
21
+ const terms = tokenizeQuery(query);
22
+ for (const skill of await this.catalog()) {
23
+ const haystack = `${skill.directoryName}\n${skill.body}`.toLowerCase();
24
+ const score = scoreSkillMatch(haystack, terms, query);
25
+ if (!query || score > 0) {
26
+ results.push({
27
+ name: skill.directoryName,
28
+ path: skill.path,
29
+ score,
30
+ summary: truncate(skill.body.replace(/\s+/g, " "), 400),
31
+ });
32
+ }
33
+ }
34
+ return results
35
+ .sort((a, b) => b.score - a.score || a.name.localeCompare(b.name))
36
+ .map(({ score: _score, ...result }) => result);
37
+ }
38
+ async metadata() {
39
+ const results = (await this.catalog()).map((skill) => {
40
+ const frontmatter = parseFrontmatter(skill.body);
41
+ return {
42
+ description: frontmatter.description || "",
43
+ name: frontmatter.name || skill.directoryName,
44
+ path: skill.path,
45
+ };
46
+ });
47
+ return results.sort((a, b) => a.name.localeCompare(b.name));
48
+ }
49
+ async read(skillPath) {
50
+ const fullPath = path.resolve(this.config.workspaceRoot, skillPath);
51
+ if (!this.skillDirs().some((dir) => fullPath === dir || fullPath.startsWith(`${dir}${path.sep}`))) {
52
+ throw new Error(`Skill path is outside configured skill directories: ${skillPath}`);
53
+ }
54
+ const relativePath = path.relative(this.config.workspaceRoot, fullPath);
55
+ const cached = this.contentCache.get(relativePath);
56
+ if (cached !== undefined)
57
+ return truncate(cached, 20000);
58
+ const body = await fs.readFile(fullPath, "utf8");
59
+ this.contentCache.set(relativePath, body);
60
+ return truncate(body, 20000);
61
+ }
62
+ async installLocal(sourcePath, requestedName) {
63
+ // Confine the source to the workspace (rejects ../ and symlink escapes) so the
64
+ // install can't copy arbitrary files (e.g. /etc/passwd) into a skill dir that skill_read exposes.
65
+ const source = await safeRealResolve(this.config.workspaceRoot, sourcePath);
66
+ const stat = await fs.stat(source);
67
+ const skillFile = stat.isDirectory() ? path.join(source, "SKILL.md") : source;
68
+ if (path.basename(skillFile) !== "SKILL.md") {
69
+ throw new Error("Skill source must be a SKILL.md file or a directory containing SKILL.md.");
70
+ }
71
+ await fs.readFile(skillFile, "utf8");
72
+ const name = sanitizeSkillName(requestedName ?? path.basename(path.dirname(skillFile)));
73
+ const installRoot = this.skillDirs()[0];
74
+ if (!installRoot)
75
+ throw new Error("No skill directories configured.");
76
+ const targetDir = path.join(installRoot, name);
77
+ const targetFile = path.join(targetDir, "SKILL.md");
78
+ await fs.mkdir(installRoot, { recursive: true });
79
+ try {
80
+ await fs.mkdir(targetDir);
81
+ }
82
+ catch (error) {
83
+ if (error.code === "EEXIST")
84
+ throw new Error(`Skill already installed: ${name}`);
85
+ throw error;
86
+ }
87
+ try {
88
+ if (stat.isDirectory())
89
+ await fs.cp(source, targetDir, { recursive: true, dereference: true });
90
+ else
91
+ await fs.copyFile(skillFile, targetFile);
92
+ }
93
+ catch (error) {
94
+ await fs.rm(targetDir, { recursive: true, force: true });
95
+ throw error;
96
+ }
97
+ this.catalogCache = undefined;
98
+ this.contentCache.clear();
99
+ return {
100
+ name,
101
+ path: path.relative(this.config.workspaceRoot, targetFile),
102
+ };
103
+ }
104
+ catalog() {
105
+ if (!this.catalogCache)
106
+ this.catalogCache = this.loadCatalog();
107
+ return this.catalogCache;
108
+ }
109
+ async loadCatalog() {
110
+ const catalog = [];
111
+ for (const dir of this.skillDirs()) {
112
+ let entries = [];
113
+ try {
114
+ entries = await fs.readdir(dir);
115
+ }
116
+ catch {
117
+ continue;
118
+ }
119
+ for (const directoryName of entries) {
120
+ const skillPath = path.join(dir, directoryName, "SKILL.md");
121
+ try {
122
+ const body = await fs.readFile(skillPath, "utf8");
123
+ const relativePath = path.relative(this.config.workspaceRoot, skillPath);
124
+ this.contentCache.set(relativePath, body);
125
+ catalog.push({ body, directoryName, path: relativePath });
126
+ }
127
+ catch {
128
+ // Ignore malformed skill folders.
129
+ }
130
+ }
131
+ }
132
+ return catalog;
133
+ }
134
+ }
135
+ function sanitizeSkillName(name) {
136
+ const normalized = name.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
137
+ if (!normalized)
138
+ throw new Error("Skill name is empty.");
139
+ return normalized;
140
+ }
141
+ function parseFrontmatter(body) {
142
+ const match = body.match(/^---\n([\s\S]*?)\n---/);
143
+ if (!match?.[1])
144
+ return {};
145
+ const metadata = {};
146
+ for (const line of match[1].split("\n")) {
147
+ const separator = line.indexOf(":");
148
+ if (separator === -1)
149
+ continue;
150
+ const key = line.slice(0, separator).trim();
151
+ const value = stripYamlString(line.slice(separator + 1).trim());
152
+ if (key === "name" || key === "description") {
153
+ metadata[key] = value;
154
+ }
155
+ }
156
+ return metadata;
157
+ }
158
+ function stripYamlString(value) {
159
+ if ((value.startsWith('"') && value.endsWith('"')) ||
160
+ (value.startsWith("'") && value.endsWith("'"))) {
161
+ return value.slice(1, -1);
162
+ }
163
+ return value;
164
+ }
165
+ function tokenizeQuery(query) {
166
+ const normalized = query.toLowerCase().trim();
167
+ if (!normalized)
168
+ return [];
169
+ const terms = new Set();
170
+ terms.add(normalized);
171
+ for (const match of normalized.matchAll(/[a-z0-9][a-z0-9_-]{1,}/g)) {
172
+ terms.add(match[0]);
173
+ }
174
+ for (const match of normalized.matchAll(/[\u3400-\u9fff]{2,}/g)) {
175
+ const text = match[0];
176
+ terms.add(text);
177
+ for (let size = 2; size <= Math.min(4, text.length); size += 1) {
178
+ for (let index = 0; index <= text.length - size; index += 1) {
179
+ terms.add(text.slice(index, index + size));
180
+ }
181
+ }
182
+ }
183
+ return [...terms].filter((term) => term.length >= 2);
184
+ }
185
+ function scoreSkillMatch(haystack, terms, query) {
186
+ if (!query)
187
+ return 1;
188
+ let score = 0;
189
+ for (const term of terms) {
190
+ if (!haystack.includes(term))
191
+ continue;
192
+ score += term.length >= 6 ? 4 : term.length >= 3 ? 2 : 1;
193
+ }
194
+ return score;
195
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Claude Code–style waiting indicator + outcome dots.
3
+ *
4
+ * Running: morphing sparkle · ✢ ✳ ✶ ✻ ✽ + shimmer label
5
+ * Success: green ● (CC uses ● / ⏺ for completed status)
6
+ * Failure: red ●
7
+ *
8
+ * Refs: SpinnerGlyph (stalled→error red), figures BLACK_CIRCLE, BriefIdleStatus ●
9
+ */
10
+ /** Filled circle — CC completed / idle status glyph. */
11
+ export declare const STATUS_DOT = "\u25CF";
12
+ export declare function spinnerGlyph(frame: number): string;
13
+ export declare function glimmerIndex(frame: number, messageWidth: number): number;
14
+ export declare function shimmerLabel(label: string, frame: number, useColor: boolean): string;
15
+ export declare function formatSpinnerGlyph(frame: number, useColor: boolean): string;
16
+ export declare const STATUS_TICK_MS = 160;
17
+ export declare function formatWaitLine(options: {
18
+ frame: number;
19
+ label: string;
20
+ elapsed: string;
21
+ useColor: boolean;
22
+ }): string;
23
+ /**
24
+ * Permanent outcome line after spinner stops (Claude Code style).
25
+ * success → green ● Thought for 3.7s
26
+ * error → red ● Failed · 3.7s
27
+ */
28
+ export declare function formatOutcomeLine(options: {
29
+ outcome: "success" | "error";
30
+ /** e.g. Thinking / Processing */
31
+ label: string;
32
+ elapsed: string;
33
+ useColor: boolean;
34
+ }): string;
35
+ export declare function blinkDiamond(frame: number, useColor: boolean): string;
36
+ export declare function diamondOn(_frame: number): boolean;
37
+ //# sourceMappingURL=status-ui.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-ui.d.ts","sourceRoot":"","sources":["../src/status-ui.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAmCH,wDAAwD;AACxD,eAAO,MAAM,UAAU,WAAM,CAAC;AAE9B,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGlD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAKxE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,CAkBpF;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,CAO3E;AAED,eAAO,MAAM,cAAc,MAAM,CAAC;AAElC,wBAAgB,cAAc,CAAC,OAAO,EAAE;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;CACnB,GAAG,MAAM,CAKT;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IACzC,OAAO,EAAE,SAAS,GAAG,OAAO,CAAC;IAC7B,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;CACnB,GAAG,MAAM,CAcT;AAeD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,CAErE;AACD,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEjD"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Claude Code–style waiting indicator + outcome dots.
3
+ *
4
+ * Running: morphing sparkle · ✢ ✳ ✶ ✻ ✽ + shimmer label
5
+ * Success: green ● (CC uses ● / ⏺ for completed status)
6
+ * Failure: red ●
7
+ *
8
+ * Refs: SpinnerGlyph (stalled→error red), figures BLACK_CIRCLE, BriefIdleStatus ●
9
+ */
10
+ const SPINNER_CHARS = ["·", "✢", "✳", "✶", "✻", "✽"];
11
+ const SPINNER_FRAMES = [
12
+ ...SPINNER_CHARS,
13
+ ...[...SPINNER_CHARS].reverse(),
14
+ ];
15
+ const GLYPH_EVERY = 3;
16
+ const SHIMMER_EVERY = 4;
17
+ const BASE_RGB = { r: 148, g: 163, b: 184 };
18
+ const SHIMMER_RGB = { r: 147, g: 197, b: 253 };
19
+ const GLYPH_RGB = { r: 96, g: 165, b: 250 };
20
+ const GREEN_RGB = { r: 34, g: 197, b: 94 }; // green-500
21
+ const RED_RGB = { r: 239, g: 68, b: 68 }; // red-500
22
+ function lerp(a, b, t) {
23
+ return Math.round(a + (b - a) * t);
24
+ }
25
+ function mixRgb(a, b, t) {
26
+ const u = Math.max(0, Math.min(1, t));
27
+ return { r: lerp(a.r, b.r, u), g: lerp(a.g, b.g, u), b: lerp(a.b, b.b, u) };
28
+ }
29
+ function rgbAnsi(c) {
30
+ return `\x1b[38;2;${c.r};${c.g};${c.b}m`;
31
+ }
32
+ const RESET = "\x1b[0m";
33
+ const DIM = "\x1b[2m";
34
+ /** Filled circle — CC completed / idle status glyph. */
35
+ export const STATUS_DOT = "●";
36
+ export function spinnerGlyph(frame) {
37
+ const i = Math.floor(frame / GLYPH_EVERY) % SPINNER_FRAMES.length;
38
+ return SPINNER_FRAMES[i] ?? "·";
39
+ }
40
+ export function glimmerIndex(frame, messageWidth) {
41
+ if (messageWidth <= 0)
42
+ return -100;
43
+ const cyclePosition = Math.floor(frame / SHIMMER_EVERY);
44
+ const cycleLength = messageWidth + 20;
45
+ return messageWidth + 10 - (cyclePosition % cycleLength);
46
+ }
47
+ export function shimmerLabel(label, frame, useColor) {
48
+ if (!useColor || label.length === 0)
49
+ return label;
50
+ const chars = [...label];
51
+ const g = glimmerIndex(frame, chars.length);
52
+ let out = "";
53
+ for (let i = 0; i < chars.length; i += 1) {
54
+ const dist = Math.abs(i - g);
55
+ let t = 0;
56
+ if (dist === 0)
57
+ t = 1;
58
+ else if (dist === 1)
59
+ t = 0.4;
60
+ else if (dist === 2)
61
+ t = 0.12;
62
+ if (t > 0) {
63
+ out += `${rgbAnsi(mixRgb(BASE_RGB, SHIMMER_RGB, t))}${chars[i]}${RESET}`;
64
+ }
65
+ else {
66
+ out += `${rgbAnsi(BASE_RGB)}${chars[i]}${RESET}`;
67
+ }
68
+ }
69
+ return out;
70
+ }
71
+ export function formatSpinnerGlyph(frame, useColor) {
72
+ const ch = spinnerGlyph(frame);
73
+ if (!useColor)
74
+ return ch;
75
+ const step = Math.floor(frame / GLYPH_EVERY) % SPINNER_FRAMES.length;
76
+ const peak = SPINNER_CHARS.length - 1;
77
+ const nearPeak = step === peak || step === SPINNER_FRAMES.length - peak - 1;
78
+ return `${rgbAnsi(nearPeak ? SHIMMER_RGB : GLYPH_RGB)}${ch}${RESET}`;
79
+ }
80
+ export const STATUS_TICK_MS = 160;
81
+ export function formatWaitLine(options) {
82
+ const glyph = formatSpinnerGlyph(options.frame, options.useColor);
83
+ const label = shimmerLabel(options.label, options.frame, options.useColor);
84
+ const elapsed = options.useColor ? `${DIM}${options.elapsed}${RESET}` : options.elapsed;
85
+ return `${glyph} ${label} ${elapsed}`;
86
+ }
87
+ /**
88
+ * Permanent outcome line after spinner stops (Claude Code style).
89
+ * success → green ● Thought for 3.7s
90
+ * error → red ● Failed · 3.7s
91
+ */
92
+ export function formatOutcomeLine(options) {
93
+ const ok = options.outcome === "success";
94
+ const color = ok ? GREEN_RGB : RED_RGB;
95
+ const dot = options.useColor ? `${rgbAnsi(color)}${STATUS_DOT}${RESET}` : STATUS_DOT;
96
+ // Past-tense style like CC “Worked for …”
97
+ const text = ok
98
+ ? pastThoughtLabel(options.label, options.elapsed)
99
+ : `Failed · ${options.elapsed}`;
100
+ const body = options.useColor
101
+ ? ok
102
+ ? `${rgbAnsi(GREEN_RGB)}${text}${RESET}`
103
+ : `${rgbAnsi(RED_RGB)}${text}${RESET}`
104
+ : text;
105
+ return `${dot} ${body}`;
106
+ }
107
+ /** Thinking → Thought for 3.7s ; Processing → Processed · 3.7s */
108
+ function pastThoughtLabel(label, elapsed) {
109
+ const base = label.replace(/\.\.\.$/, "").trim();
110
+ if (/^thinking$/i.test(base))
111
+ return `Thought for ${elapsed}`;
112
+ if (/^processing$/i.test(base))
113
+ return `Processed · ${elapsed}`;
114
+ if (/^working$/i.test(base))
115
+ return `Worked for ${elapsed}`;
116
+ if (/^considering$/i.test(base))
117
+ return `Considered · ${elapsed}`;
118
+ if (/^composing$/i.test(base))
119
+ return `Composed · ${elapsed}`;
120
+ // tool name mid-flight
121
+ if (/^[a-z_]+$/i.test(base) && !/ing$/i.test(base))
122
+ return `Used ${base} · ${elapsed}`;
123
+ return `${base} · ${elapsed}`;
124
+ }
125
+ export function blinkDiamond(frame, useColor) {
126
+ return formatSpinnerGlyph(frame, useColor);
127
+ }
128
+ export function diamondOn(_frame) {
129
+ return true;
130
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Terminal collapsible Thought blocks (like web ProcessTimeline).
3
+ * Default: one line “◆ Thought for 0.5s ▸”
4
+ * Expand: /thought or key `t` → show detail lines under it.
5
+ */
6
+ export type ThoughtBlock = {
7
+ id: string;
8
+ startedAt: number;
9
+ endedAt?: number;
10
+ /** Short lines shown when expanded */
11
+ lines: string[];
12
+ tools: string[];
13
+ expanded: boolean;
14
+ /** Printed to terminal already (collapsed header) */
15
+ printed: boolean;
16
+ /** true if turn failed */
17
+ failed?: boolean;
18
+ };
19
+ export type ThoughtFoldOptions = {
20
+ color: boolean;
21
+ out: (text: string) => void;
22
+ };
23
+ export declare class ThoughtFoldManager {
24
+ private blocks;
25
+ private current;
26
+ private options;
27
+ constructor(options: ThoughtFoldOptions);
28
+ setColor(color: boolean): void;
29
+ /** Start a new thought window for this user turn. */
30
+ beginTurn(): void;
31
+ note(line: string): void;
32
+ noteTool(tool: string): void;
33
+ /**
34
+ * Finalize current thought and print collapsed header (once).
35
+ * Call before streaming the final answer, or at end of turn.
36
+ */
37
+ finalizeAndPrint(defaultDetail?: string, failed?: boolean, printHeader?: boolean): ThoughtBlock | null;
38
+ /** Toggle expand/collapse of the last thought (or by id). */
39
+ toggle(id?: string): void;
40
+ expandLast(): void;
41
+ lastSummary(): string | null;
42
+ clear(): void;
43
+ private duration;
44
+ private printHeader;
45
+ private printExpanded;
46
+ private dim;
47
+ }
48
+ //# sourceMappingURL=thought-fold.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thought-fold.d.ts","sourceRoot":"","sources":["../src/thought-fold.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sCAAsC;IACtC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,qDAAqD;IACrD,OAAO,EAAE,OAAO,CAAC;IACjB,0BAA0B;IAC1B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B,CAAC;AAQF,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,OAAO,CAAqB;gBAExB,OAAO,EAAE,kBAAkB;IAIvC,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAI9B,qDAAqD;IACrD,SAAS,IAAI,IAAI;IAWjB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IASxB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAM5B;;;OAGG;IACH,gBAAgB,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,MAAM,UAAQ,EAAE,WAAW,UAAQ,GAAG,YAAY,GAAG,IAAI;IA0BlG,6DAA6D;IAC7D,MAAM,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI;IAgBzB,UAAU,IAAI,IAAI;IAclB,WAAW,IAAI,MAAM,GAAG,IAAI;IAO5B,KAAK,IAAI,IAAI;IAKb,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,GAAG;CAIZ"}
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Terminal collapsible Thought blocks (like web ProcessTimeline).
3
+ * Default: one line “◆ Thought for 0.5s ▸”
4
+ * Expand: /thought or key `t` → show detail lines under it.
5
+ */
6
+ const DIM = "\x1b[90m";
7
+ const RESET = "\x1b[0m";
8
+ const AMBER = "\x1b[38;2;251;191;36m";
9
+ const GREEN = "\x1b[32m";
10
+ const RED = "\x1b[31m";
11
+ export class ThoughtFoldManager {
12
+ blocks = [];
13
+ current = null;
14
+ options;
15
+ constructor(options) {
16
+ this.options = options;
17
+ }
18
+ setColor(color) {
19
+ this.options.color = color;
20
+ }
21
+ /** Start a new thought window for this user turn. */
22
+ beginTurn() {
23
+ this.current = {
24
+ id: `th-${Date.now()}`,
25
+ startedAt: Date.now(),
26
+ lines: [],
27
+ tools: [],
28
+ expanded: false,
29
+ printed: false,
30
+ };
31
+ }
32
+ note(line) {
33
+ if (!this.current)
34
+ return;
35
+ const t = line.trim();
36
+ if (!t)
37
+ return;
38
+ // de-dupe consecutive
39
+ if (this.current.lines[this.current.lines.length - 1] === t)
40
+ return;
41
+ this.current.lines.push(t);
42
+ }
43
+ noteTool(tool) {
44
+ if (!this.current)
45
+ return;
46
+ if (!this.current.tools.includes(tool))
47
+ this.current.tools.push(tool);
48
+ this.note(`used ${tool}`);
49
+ }
50
+ /**
51
+ * Finalize current thought and print collapsed header (once).
52
+ * Call before streaming the final answer, or at end of turn.
53
+ */
54
+ finalizeAndPrint(defaultDetail, failed = false, printHeader = false) {
55
+ if (!this.current)
56
+ return null;
57
+ if (this.current.printed)
58
+ return this.current;
59
+ this.current.endedAt = Date.now();
60
+ this.current.failed = failed;
61
+ if (this.current.lines.length === 0 && defaultDetail) {
62
+ this.current.lines.push(defaultDetail);
63
+ }
64
+ if (this.current.lines.length === 0) {
65
+ this.current.lines.push("Processed the request and prepared a reply.");
66
+ }
67
+ this.blocks.push(this.current);
68
+ // Only the most recent block is ever toggled/expanded; cap history so a long-lived
69
+ // interactive session doesn't grow this array (and its retained lines) without bound.
70
+ if (this.blocks.length > 50)
71
+ this.blocks.splice(0, this.blocks.length - 50);
72
+ if (printHeader) {
73
+ this.printHeader(this.current);
74
+ this.current.printed = true;
75
+ }
76
+ const done = this.current;
77
+ this.current = null;
78
+ return done;
79
+ }
80
+ /** Toggle expand/collapse of the last thought (or by id). */
81
+ toggle(id) {
82
+ const block = id
83
+ ? this.blocks.find((b) => b.id === id)
84
+ : this.blocks[this.blocks.length - 1];
85
+ if (!block) {
86
+ this.options.out(this.dim("(no thought to expand yet)\n"));
87
+ return;
88
+ }
89
+ block.expanded = !block.expanded;
90
+ if (block.expanded) {
91
+ this.printExpanded(block);
92
+ }
93
+ else {
94
+ this.options.out(this.dim(" (thought collapsed — /thought to expand again)\n"));
95
+ }
96
+ }
97
+ expandLast() {
98
+ const block = this.blocks[this.blocks.length - 1];
99
+ if (!block) {
100
+ this.options.out(this.dim("(no thought to expand yet)\n"));
101
+ return;
102
+ }
103
+ if (!block.expanded) {
104
+ block.expanded = true;
105
+ this.printExpanded(block);
106
+ }
107
+ else {
108
+ this.printExpanded(block); // re-print
109
+ }
110
+ }
111
+ lastSummary() {
112
+ const b = this.blocks[this.blocks.length - 1];
113
+ if (!b)
114
+ return null;
115
+ const sec = ((b.endedAt ?? Date.now()) - b.startedAt) / 1000;
116
+ return `Thought for ${sec.toFixed(1)}s · ${b.tools.length} tools · ${b.expanded ? "expanded" : "collapsed"}`;
117
+ }
118
+ clear() {
119
+ this.blocks = [];
120
+ this.current = null;
121
+ }
122
+ duration(block) {
123
+ const ms = (block.endedAt ?? Date.now()) - block.startedAt;
124
+ return `${(ms / 1000).toFixed(1)}s`;
125
+ }
126
+ printHeader(block) {
127
+ const arrow = block.expanded ? "▾" : "▸";
128
+ // CC style: green ● success, red ● failure (not amber while done)
129
+ const color = block.failed ? RED : GREEN;
130
+ const dot = this.options.color ? `${color}●${RESET}` : "●";
131
+ const label = block.failed
132
+ ? `Failed · ${this.duration(block)}`
133
+ : `Thought for ${this.duration(block)}`;
134
+ const labelPainted = this.options.color ? `${color}${label}${RESET}` : label;
135
+ const hint = this.dim(` ${arrow} /thought to expand`);
136
+ this.options.out(`\n${dot} ${labelPainted}${hint}\n`);
137
+ }
138
+ printExpanded(block) {
139
+ const color = block.failed ? RED : GREEN;
140
+ const dot = this.options.color ? `${color}●${RESET}` : "●";
141
+ const head = block.failed ? `Failed · ${this.duration(block)}` : `Thought for ${this.duration(block)}`;
142
+ this.options.out(`\n${dot} ${head} ▾\n`);
143
+ if (block.tools.length) {
144
+ this.options.out(this.dim(` tools: ${block.tools.join(", ")}\n`));
145
+ }
146
+ for (const line of block.lines) {
147
+ this.options.out(this.dim(` │ ${line}\n`));
148
+ }
149
+ this.options.out(this.dim(" └ /thought to collapse\n\n"));
150
+ }
151
+ dim(text) {
152
+ if (!this.options.color)
153
+ return text;
154
+ return `${DIM}${text}${RESET}`;
155
+ }
156
+ }
@@ -0,0 +1,27 @@
1
+ import type { AgentConfig, SubagentConfig, TraceSink } from "../types.js";
2
+ import { SkillManager } from "../skills/skills.js";
3
+ import { McpManager } from "../mcp/manager.js";
4
+ import { ToolRegistry } from "./registry.js";
5
+ type AddressResolver = (hostname: string) => Promise<Array<{
6
+ address: string;
7
+ family: number;
8
+ }>>;
9
+ export type RunTaskFn = (agent: SubagentConfig, prompt: string, emit: TraceSink) => Promise<string>;
10
+ type BuiltinOptions = {
11
+ config: AgentConfig;
12
+ skillManager: SkillManager;
13
+ mcpManager: McpManager;
14
+ /** Main agent only — launch Claude Code–style Task subagents */
15
+ runTask?: RunTaskFn;
16
+ /** When false, Task tool is not registered (subagents never nest) */
17
+ enableTask?: boolean;
18
+ /** Tool allowlist for subagents; ["*"] or omit = all registered tools */
19
+ allowedTools?: string[];
20
+ };
21
+ export declare function createBuiltinRegistry(options: BuiltinOptions): ToolRegistry;
22
+ export declare function resolvePublicHttpAddress(url: URL, resolve?: AddressResolver): Promise<{
23
+ address: string;
24
+ family: 4 | 6;
25
+ }>;
26
+ export {};
27
+ //# sourceMappingURL=builtin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builtin.d.ts","sourceRoot":"","sources":["../../src/tools/builtin.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAc,cAAc,EAAwB,SAAS,EAAE,MAAM,aAAa,CAAC;AAE5G,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAY7C,KAAK,eAAe,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAAC;AAGjG,MAAM,MAAM,SAAS,GAAG,CACtB,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,SAAS,KACZ,OAAO,CAAC,MAAM,CAAC,CAAC;AAErB,KAAK,cAAc,GAAG;IACpB,MAAM,EAAE,WAAW,CAAC;IACpB,YAAY,EAAE,YAAY,CAAC;IAC3B,UAAU,EAAE,UAAU,CAAC;IACvB,gEAAgE;IAChE,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,qEAAqE;IACrE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,cAAc,GAAG,YAAY,CA0hB3E;AAsKD,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,eAA2E,GACnF,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAA;CAAE,CAAC,CAkB7C"}