@iaforged/context-code 1.2.8 → 1.2.10

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 (172) hide show
  1. package/README.md +119 -119
  2. package/context-bootstrap.js +26 -26
  3. package/dist/src/QueryEngine.js +394 -327
  4. package/dist/src/bridge/bridgeUI.js +1 -1
  5. package/dist/src/buddy/prompt.js +4 -4
  6. package/dist/src/cli/handlers/auth.js +126 -9
  7. package/dist/src/cli/print.js +35 -1
  8. package/dist/src/commands/agent/agent.js +28 -2
  9. package/dist/src/commands/agent/agentStore.js +8 -1
  10. package/dist/src/commands/agent/index.js +1 -1
  11. package/dist/src/commands/bridge-kick.js +9 -9
  12. package/dist/src/commands/commit.js +34 -34
  13. package/dist/src/commands/init-verifiers.js +3 -3
  14. package/dist/src/commands/init.js +88 -88
  15. package/dist/src/commands/insights.js +787 -787
  16. package/dist/src/commands/install.js +19 -19
  17. package/dist/src/commands/login/login.js +21 -12
  18. package/dist/src/commands/logout/logout.js +9 -0
  19. package/dist/src/commands/model/model.js +9 -4
  20. package/dist/src/commands/orchestrate/SwarmUI.js +50 -0
  21. package/dist/src/commands/orchestrate/index.js +2 -2
  22. package/dist/src/commands/orchestrate/orchestrate.js +708 -12
  23. package/dist/src/commands/pr_comments/index.js +33 -33
  24. package/dist/src/commands/profile/index.js +1 -1
  25. package/dist/src/commands/profile/profile.js +52 -3
  26. package/dist/src/commands/provider/index.js +1 -1
  27. package/dist/src/commands/provider/provider.js +117 -45
  28. package/dist/src/commands/resumen/index.js +9 -0
  29. package/dist/src/commands/resumen/resumen.js +29 -0
  30. package/dist/src/commands/security-review.js +190 -190
  31. package/dist/src/commands/swarm-auto/index.js +9 -0
  32. package/dist/src/commands/swarm-auto/swarmAuto.js +111 -0
  33. package/dist/src/commands/swarm-init/index.js +9 -0
  34. package/dist/src/commands/swarm-init/swarmInit.js +72 -0
  35. package/dist/src/commands/team/team.js +39 -6
  36. package/dist/src/commands.js +14 -0
  37. package/dist/src/components/LogoV2/CondensedLogo.js +2 -2
  38. package/dist/src/components/PromptInput/PromptInputQueuedCommands.js +3 -3
  39. package/dist/src/components/agents/agentFileUtils.js +6 -6
  40. package/dist/src/components/permissions/hooks.js +5 -5
  41. package/dist/src/constants/outputStyles.js +83 -83
  42. package/dist/src/core/agents/blueprints.js +58 -0
  43. package/dist/src/core/agents/cliAdapter.js +61 -0
  44. package/dist/src/core/agents/registry.js +93 -0
  45. package/dist/src/core/agents/runtime.js +4 -0
  46. package/dist/src/core/agents/runtime.smoke.js +42 -0
  47. package/dist/src/core/agents/swarm.smoke.js +48 -0
  48. package/dist/src/core/agents/swarmTools.js +38 -0
  49. package/dist/src/core/auth/index.js +2 -0
  50. package/dist/src/core/auth/loginCliAdapter.js +24 -0
  51. package/dist/src/core/auth/loginCore.js +67 -0
  52. package/dist/src/core/auth/logoutCliAdapter.js +34 -0
  53. package/dist/src/core/auth/logoutCore.js +52 -0
  54. package/dist/src/core/auth/preflight.smoke.js +151 -0
  55. package/dist/src/core/index.js +21 -0
  56. package/dist/src/core/mcp/blueprints.js +27 -0
  57. package/dist/src/core/mcp/common.js +14 -0
  58. package/dist/src/core/mcp/runtime.js +67 -0
  59. package/dist/src/core/mcp/runtime.smoke.js +50 -0
  60. package/dist/src/core/mcp/swarmClient.js +40 -0
  61. package/dist/src/core/mcp/swarmSetup.js +43 -0
  62. package/dist/src/core/providers/cliAdapter.js +39 -0
  63. package/dist/src/core/providers/contracts.js +1 -0
  64. package/dist/src/core/providers/index.js +3 -0
  65. package/dist/src/core/providers/llmCore.js +123 -0
  66. package/dist/src/core/providers/providerCore.js +141 -0
  67. package/dist/src/core/providers/providerModelCompatibility.js +98 -0
  68. package/dist/src/core/providers/providerParitySmoke.js +83 -0
  69. package/dist/src/core/providers/providerProfileModelSmoke.js +80 -0
  70. package/dist/src/core/query/contracts.js +1 -0
  71. package/dist/src/core/query/runtime.js +117 -0
  72. package/dist/src/core/query/runtime.smoke.js +39 -0
  73. package/dist/src/core/query/timelineThinking.smoke.js +25 -0
  74. package/dist/src/core/query/wiring.smoke.js +76 -0
  75. package/dist/src/core/skills/cliAdapter.js +38 -0
  76. package/dist/src/core/skills/index.js +52 -0
  77. package/dist/src/core/skills/runtime.smoke.js +53 -0
  78. package/dist/src/core/tasks/runtime.js +205 -0
  79. package/dist/src/core/tasks/runtime.smoke.js +63 -0
  80. package/dist/src/core/tasks/sdkAdapter.js +4 -0
  81. package/dist/src/core/tools/contracts.js +3 -0
  82. package/dist/src/core/tools/fileResolution.js +112 -0
  83. package/dist/src/core/tools/fileResolution.smoke.js +33 -0
  84. package/dist/src/core/tools/filesCore.js +51 -0
  85. package/dist/src/core/tools/filesCore.smoke.js +108 -0
  86. package/dist/src/core/tools/gitCore.js +20 -0
  87. package/dist/src/core/tools/imageParity.smoke.js +36 -0
  88. package/dist/src/core/tools/notebookParity.smoke.js +68 -0
  89. package/dist/src/core/tools/registry.js +22 -0
  90. package/dist/src/core/tools/runtime.smoke.js +32 -0
  91. package/dist/src/core/tools/shellCore.js +60 -0
  92. package/dist/src/core/types/agentContext.js +9 -0
  93. package/dist/src/core/types/auth.js +3 -0
  94. package/dist/src/core/types/command.js +13 -0
  95. package/dist/src/core/types/provider.js +3 -0
  96. package/dist/src/core/types/sdkEvent.js +10 -0
  97. package/dist/src/core/types/swarm.js +1 -0
  98. package/dist/src/cost-tracker.js +3 -3
  99. package/dist/src/hooks/useAwaySummary.js +22 -9
  100. package/dist/src/main.js +32 -2
  101. package/dist/src/screens/REPL.js +9 -0
  102. package/dist/src/services/AgentSummary/agentSummary.js +10 -10
  103. package/dist/src/services/autoDream/autoDream.js +5 -5
  104. package/dist/src/services/autoDream/consolidationPrompt.js +49 -49
  105. package/dist/src/services/compact/prompt.js +238 -238
  106. package/dist/src/services/limits/sessionCounter.js +17 -17
  107. package/dist/src/services/mcp/client.js +27 -1
  108. package/dist/src/services/orchestration/execution/AgentTaskExecutor.js +39 -20
  109. package/dist/src/services/orchestration/execution/OrchestrationExecutionRuntime.js +65 -58
  110. package/dist/src/skills/bundled/loop.js +57 -57
  111. package/dist/src/skills/bundled/remember.js +53 -53
  112. package/dist/src/skills/bundled/simplify.js +49 -49
  113. package/dist/src/skills/bundled/skillify.js +2 -2
  114. package/dist/src/state/onChangeAppState.js +6 -0
  115. package/dist/src/tasks/LocalAgentTask/LocalAgentTask.js +5 -5
  116. package/dist/src/tasks/LocalMainSessionTask.js +5 -5
  117. package/dist/src/tasks/LocalShellTask/LocalShellTask.js +13 -13
  118. package/dist/src/tools/AgentTool/forkSubagent.js +25 -25
  119. package/dist/src/tools/AskUserQuestionTool/prompt.js +29 -29
  120. package/dist/src/tools/BashTool/BashTool.js +27 -2
  121. package/dist/src/tools/BriefTool/prompt.js +14 -14
  122. package/dist/src/tools/EnterPlanModeTool/EnterPlanModeTool.js +12 -12
  123. package/dist/src/tools/EnterPlanModeTool/prompt.js +140 -140
  124. package/dist/src/tools/ExitPlanModeTool/ExitPlanModeV2Tool.js +18 -18
  125. package/dist/src/tools/ExitPlanModeTool/prompt.js +23 -23
  126. package/dist/src/tools/ExitWorktreeTool/prompt.js +29 -29
  127. package/dist/src/tools/FileEditTool/prompt.js +7 -7
  128. package/dist/src/tools/FileReadTool/FileReadTool.js +18 -1
  129. package/dist/src/tools/FileWriteTool/prompt.js +6 -6
  130. package/dist/src/tools/GlobTool/prompt.js +4 -4
  131. package/dist/src/tools/GrepTool/prompt.js +10 -10
  132. package/dist/src/tools/LSPTool/prompt.js +18 -18
  133. package/dist/src/tools/ListMcpResourcesTool/prompt.js +15 -15
  134. package/dist/src/tools/PowerShellTool/PowerShellTool.js +25 -2
  135. package/dist/src/tools/ReadMcpResourceTool/prompt.js +13 -13
  136. package/dist/src/tools/SendMessageTool/prompt.js +36 -36
  137. package/dist/src/tools/SkillTool/prompt.js +21 -21
  138. package/dist/src/tools/SleepTool/prompt.js +10 -10
  139. package/dist/src/tools/TaskCreateTool/prompt.js +41 -41
  140. package/dist/src/tools/TaskGetTool/prompt.js +21 -21
  141. package/dist/src/tools/TaskListTool/prompt.js +30 -30
  142. package/dist/src/tools/TaskOutputTool/TaskOutputTool.js +8 -8
  143. package/dist/src/tools/TaskStopTool/prompt.js +5 -5
  144. package/dist/src/tools/TaskUpdateTool/prompt.js +74 -74
  145. package/dist/src/tools/TodoWriteTool/prompt.js +178 -178
  146. package/dist/src/tools/ToolSearchTool/prompt.js +9 -9
  147. package/dist/src/tools/WebFetchTool/WebFetchTool.js +9 -9
  148. package/dist/src/tools/WebFetchTool/prompt.js +31 -31
  149. package/dist/src/tools/WebSearchTool/prompt.js +26 -26
  150. package/dist/src/utils/agentContext.js +2 -0
  151. package/dist/src/utils/agenticSessionSearch.js +38 -38
  152. package/dist/src/utils/config.js +2 -0
  153. package/dist/src/utils/genericProcessUtils.js +21 -21
  154. package/dist/src/utils/heapDumpService.js +4 -4
  155. package/dist/src/utils/mcpValidation.js +2 -2
  156. package/dist/src/utils/model/modelStrings.js +1 -1
  157. package/dist/src/utils/model/providers.js +5 -0
  158. package/dist/src/utils/orchestration/store/providerAgentStore.js +22 -22
  159. package/dist/src/utils/orchestration/store/providerWorkspaceStore.js +10 -10
  160. package/dist/src/utils/orchestration/store/runStore.js +68 -68
  161. package/dist/src/utils/orchestration/store/teamStore.js +28 -28
  162. package/dist/src/utils/permissions/permissionExplainer.js +6 -6
  163. package/dist/src/utils/permissions/permissionsDb.js +43 -43
  164. package/dist/src/utils/sdkEventQueue.js +2 -0
  165. package/dist/src/utils/secureStorage/sqliteStorage.js +12 -12
  166. package/dist/src/utils/standardMcp/common.js +15 -0
  167. package/dist/src/utils/standardMcp/setup.js +52 -0
  168. package/dist/src/utils/swarm/teammatePromptAddendum.js +10 -10
  169. package/dist/src/utils/task/framework.js +6 -6
  170. package/package.json +1 -1
  171. package/dist/src/commands/usage/index.js +0 -7
  172. package/dist/src/commands/usage/usage.js +0 -5
@@ -0,0 +1,112 @@
1
+ // Helpers puros de resolucion/sugerencia de paths para FileReadTool.
2
+ //
3
+ // Estos vivian dispersos en `utils/file.ts`, `utils/pdfUtils.ts` y dentro de
4
+ // `tools/FileReadTool/FileReadTool.ts`. La meta de Fase 6 es que el `core/`
5
+ // no dependa de globals del CLI (`getCwd`, `getFsImplementation`, modelo
6
+ // activo). Aqui aceptamos esas dependencias por parametro o por context.
7
+ import { dirname, basename, extname, join, relative, sep } from 'path';
8
+ import { realpath, stat } from 'fs/promises';
9
+ const REGULAR_SPACE = ' ';
10
+ const THIN_SPACE = ' ';
11
+ // ── Document extension helpers ──────────────────────────────────────────────
12
+ export const CORE_DOCUMENT_EXTENSIONS = new Set(['pdf']);
13
+ export function isPDFExtension(ext) {
14
+ const normalized = ext.startsWith('.') ? ext.slice(1) : ext;
15
+ return CORE_DOCUMENT_EXTENSIONS.has(normalized.toLowerCase());
16
+ }
17
+ // ── PDF page range parsing (puro string) ────────────────────────────────────
18
+ export function parsePDFPageRange(pages) {
19
+ const trimmed = pages.trim();
20
+ if (!trimmed) {
21
+ return null;
22
+ }
23
+ // "N-" open-ended range
24
+ if (trimmed.endsWith('-')) {
25
+ const first = parseInt(trimmed.slice(0, -1), 10);
26
+ if (isNaN(first) || first < 1) {
27
+ return null;
28
+ }
29
+ return { firstPage: first, lastPage: Infinity };
30
+ }
31
+ const dashIndex = trimmed.indexOf('-');
32
+ if (dashIndex === -1) {
33
+ const page = parseInt(trimmed, 10);
34
+ if (isNaN(page) || page < 1) {
35
+ return null;
36
+ }
37
+ return { firstPage: page, lastPage: page };
38
+ }
39
+ const first = parseInt(trimmed.slice(0, dashIndex), 10);
40
+ const last = parseInt(trimmed.slice(dashIndex + 1), 10);
41
+ if (isNaN(first) || isNaN(last) || first < 1 || last < 1 || last < first) {
42
+ return null;
43
+ }
44
+ return { firstPage: first, lastPage: last };
45
+ }
46
+ // ── macOS screenshot AM/PM space variant ────────────────────────────────────
47
+ //
48
+ // Para macOS, los screenshots a veces usan espacio normal y otras un thin
49
+ // space (U+202F) antes de AM/PM. Esto tira ENOENT al leer si el modelo
50
+ // adivina mal el espacio. Devolvemos el path alternativo si aplica.
51
+ export function getAlternateScreenshotPath(filePath) {
52
+ const filename = basename(filePath);
53
+ const amPmPattern = new RegExp(`^(.+)([${REGULAR_SPACE}${THIN_SPACE}])(AM|PM)(\\.png)$`);
54
+ const match = filename.match(amPmPattern);
55
+ if (!match)
56
+ return undefined;
57
+ const currentSpace = match[2];
58
+ const alternateSpace = currentSpace === REGULAR_SPACE ? THIN_SPACE : REGULAR_SPACE;
59
+ return filePath.replace(`${currentSpace}${match[3]}${match[4]}`, `${alternateSpace}${match[3]}${match[4]}`);
60
+ }
61
+ export function findSimilarFile(filePath, readdirSync, isENOENT, logError) {
62
+ try {
63
+ const dir = dirname(filePath);
64
+ const fileBaseName = basename(filePath, extname(filePath));
65
+ const files = readdirSync(dir);
66
+ const similarFiles = files.filter(file => basename(file.name, extname(file.name)) === fileBaseName &&
67
+ join(dir, file.name) !== filePath);
68
+ const firstMatch = similarFiles[0];
69
+ if (firstMatch) {
70
+ return firstMatch.name;
71
+ }
72
+ return undefined;
73
+ }
74
+ catch (error) {
75
+ if (!isENOENT(error)) {
76
+ logError(error);
77
+ }
78
+ return undefined;
79
+ }
80
+ }
81
+ // ── Path-under-cwd suggestion (dropped repo folder) ─────────────────────────
82
+ //
83
+ // Patron comun: el modelo construye un path absoluto omitiendo el directorio
84
+ // del repo. Si el path no existe pero existe `cwd/<rel>`, devolvemos esa
85
+ // version. cwd se inyecta para evitar dependencia con `process.cwd()` global.
86
+ export const FILE_NOT_FOUND_CWD_NOTE = 'Note: your current working directory is';
87
+ export async function suggestPathUnderCwd(requestedPath, cwd) {
88
+ const cwdParent = dirname(cwd);
89
+ let resolvedPath = requestedPath;
90
+ try {
91
+ const resolvedDir = await realpath(dirname(requestedPath));
92
+ resolvedPath = join(resolvedDir, basename(requestedPath));
93
+ }
94
+ catch {
95
+ // Parent directory doesn't exist, use the original path
96
+ }
97
+ const cwdParentPrefix = cwdParent === sep ? sep : cwdParent + sep;
98
+ if (!resolvedPath.startsWith(cwdParentPrefix) ||
99
+ resolvedPath.startsWith(cwd + sep) ||
100
+ resolvedPath === cwd) {
101
+ return undefined;
102
+ }
103
+ const relFromParent = relative(cwdParent, resolvedPath);
104
+ const correctedPath = join(cwd, relFromParent);
105
+ try {
106
+ await stat(correctedPath);
107
+ return correctedPath;
108
+ }
109
+ catch {
110
+ return undefined;
111
+ }
112
+ }
@@ -0,0 +1,33 @@
1
+ // Smoke de helpers puros del core/tools.
2
+ //
3
+ // Garantiza paridad con la logica original de FileReadTool sin requerir
4
+ // dependencias del CLI (modelo, fs global, etc).
5
+ import assert from 'node:assert/strict';
6
+ import { parsePDFPageRange, isPDFExtension, getAlternateScreenshotPath, } from './fileResolution.js';
7
+ // ── parsePDFPageRange ──
8
+ assert.deepEqual(parsePDFPageRange('5'), { firstPage: 5, lastPage: 5 });
9
+ assert.deepEqual(parsePDFPageRange('1-10'), { firstPage: 1, lastPage: 10 });
10
+ assert.deepEqual(parsePDFPageRange('3-'), { firstPage: 3, lastPage: Infinity });
11
+ assert.equal(parsePDFPageRange(''), null, 'empty input');
12
+ assert.equal(parsePDFPageRange('abc'), null, 'non-numeric');
13
+ assert.equal(parsePDFPageRange('0'), null, 'zero rejected');
14
+ assert.equal(parsePDFPageRange('10-1'), null, 'inverted range');
15
+ assert.equal(parsePDFPageRange('-5'), null, 'leading dash');
16
+ // ── isPDFExtension ──
17
+ assert.equal(isPDFExtension('pdf'), true);
18
+ assert.equal(isPDFExtension('.pdf'), true);
19
+ assert.equal(isPDFExtension('PDF'), true);
20
+ assert.equal(isPDFExtension('txt'), false);
21
+ assert.equal(isPDFExtension(''), false);
22
+ // ── getAlternateScreenshotPath ──
23
+ const regularSpace = ' ';
24
+ const thinSpace = ' ';
25
+ const regularPath = `/tmp/Screenshot 2024-01-01 at 12.00.00${regularSpace}AM.png`;
26
+ const expectedAlt = `/tmp/Screenshot 2024-01-01 at 12.00.00${thinSpace}AM.png`;
27
+ assert.equal(getAlternateScreenshotPath(regularPath), expectedAlt, 'regular -> thin space variant');
28
+ const thinPath = `/tmp/Screenshot 2024-01-01 at 12.00.00${thinSpace}PM.png`;
29
+ const expectedAlt2 = `/tmp/Screenshot 2024-01-01 at 12.00.00${regularSpace}PM.png`;
30
+ assert.equal(getAlternateScreenshotPath(thinPath), expectedAlt2, 'thin -> regular space variant');
31
+ assert.equal(getAlternateScreenshotPath('/tmp/regular.png'), undefined, 'non-screenshot returns undefined');
32
+ assert.equal(getAlternateScreenshotPath('/tmp/foo.txt'), undefined, 'non-png returns undefined');
33
+ console.log('fileResolution smoke PASS');
@@ -0,0 +1,51 @@
1
+ import { readFileInRange } from '../../utils/readFileInRange.js';
2
+ function normalizeOffset(offset) {
3
+ if (typeof offset !== 'number' || Number.isNaN(offset)) {
4
+ return 1;
5
+ }
6
+ return Math.max(0, Math.trunc(offset));
7
+ }
8
+ export async function executeCoreFileRead(input, context = {}) {
9
+ try {
10
+ const targetPath = input.absolute_path;
11
+ if (!targetPath) {
12
+ return { success: false, error: 'absolute_path is required' };
13
+ }
14
+ const startLine = normalizeOffset(input.offset);
15
+ const readTextFileRange = context.readTextFileRange ??
16
+ (async ({ absolute_path, offset, limit, max_bytes, }) => {
17
+ const effectiveStartLine = normalizeOffset(offset);
18
+ const lineOffset = effectiveStartLine === 0 ? 0 : effectiveStartLine - 1;
19
+ const result = await readFileInRange(absolute_path, lineOffset, limit, max_bytes, context.abortSignal);
20
+ return {
21
+ filePath: absolute_path,
22
+ content: result.content,
23
+ lineCount: result.lineCount,
24
+ totalLines: result.totalLines,
25
+ totalBytes: result.totalBytes,
26
+ readBytes: result.readBytes,
27
+ mtimeMs: result.mtimeMs,
28
+ startLine: effectiveStartLine,
29
+ };
30
+ });
31
+ const data = await readTextFileRange({
32
+ absolute_path: targetPath,
33
+ offset: startLine,
34
+ limit: input.limit,
35
+ max_bytes: input.max_bytes,
36
+ });
37
+ return {
38
+ success: true,
39
+ output: data.content,
40
+ data,
41
+ };
42
+ }
43
+ catch (err) {
44
+ return { success: false, error: String(err) };
45
+ }
46
+ }
47
+ export const coreFileReadTool = {
48
+ name: 'Read',
49
+ description: 'Reads text file contents from the filesystem with optional offset and limit.',
50
+ execute: (input, context) => executeCoreFileRead(input, context),
51
+ };
@@ -0,0 +1,108 @@
1
+ // Smoke del orquestador executeCoreFileRead: valida que el ruteo por
2
+ // extension elige la rama correcta y que los readers inyectables se llaman
3
+ // con los argumentos esperados. No depende de fs real ni de poppler/sharp.
4
+ import assert from 'node:assert/strict';
5
+ import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ import { tmpdir } from 'node:os';
8
+ import { executeCoreFileRead } from './filesCore.js';
9
+ // ── Texto plano: usa fs real, sin readers inyectados ──
10
+ const tmpDir = mkdtempSync(join(tmpdir(), 'core-tools-smoke-'));
11
+ try {
12
+ const txt = join(tmpDir, 'hello.txt');
13
+ writeFileSync(txt, 'line1\nline2\nline3\n');
14
+ const result = await executeCoreFileRead({ absolute_path: txt });
15
+ assert.equal(result.success, true, 'text read success');
16
+ assert.ok(result.data, 'text result has data');
17
+ const data = result.data;
18
+ assert.equal(data.type, 'text');
19
+ assert.match(data.content, /line1/);
20
+ // ── Dedup: segundo call con misma offset/limit + mtime → file_unchanged ──
21
+ const store = new Map();
22
+ const ctx = {
23
+ readFileState: {
24
+ get: (p) => store.get(p),
25
+ set: (p, e) => store.set(p, e),
26
+ },
27
+ };
28
+ const first = await executeCoreFileRead({ absolute_path: txt }, ctx);
29
+ assert.equal(first.success, true);
30
+ const second = await executeCoreFileRead({ absolute_path: txt }, ctx);
31
+ assert.equal(second.success, true);
32
+ const secondData = second.data;
33
+ assert.equal(secondData.type, 'file_unchanged', 'dedup hit on second read');
34
+ }
35
+ finally {
36
+ rmSync(tmpDir, { recursive: true, force: true });
37
+ }
38
+ // ── Notebook: el orquestador llama al reader inyectado ──
39
+ const notebookCalls = [];
40
+ const notebookReader = async (path) => {
41
+ notebookCalls.push(path);
42
+ return [{ cell_type: 'code', source: 'print(1)' }];
43
+ };
44
+ const nbResult = await executeCoreFileRead({ absolute_path: '/fake/notebook.ipynb' }, { readNotebook: notebookReader });
45
+ assert.equal(nbResult.success, true);
46
+ assert.deepEqual(notebookCalls, ['/fake/notebook.ipynb']);
47
+ const nbData = nbResult.data;
48
+ assert.equal(nbData.type, 'notebook');
49
+ // ── Image: el orquestador llama al reader inyectado con maxTokens default ──
50
+ const imageCalls = [];
51
+ const imageReader = async (p, maxTokens) => {
52
+ imageCalls.push({ p, max: maxTokens });
53
+ return {
54
+ type: 'image',
55
+ file: {
56
+ base64: 'AAAA',
57
+ mediaType: 'image/png',
58
+ originalSize: 4,
59
+ },
60
+ };
61
+ };
62
+ const imgResult = await executeCoreFileRead({ absolute_path: '/fake/photo.png' }, { readImage: imageReader });
63
+ assert.equal(imgResult.success, true);
64
+ assert.equal(imageCalls.length, 1);
65
+ assert.equal(imageCalls[0].p, '/fake/photo.png');
66
+ assert.ok(imageCalls[0].max > 0, 'maxTokens default applied');
67
+ // ── Image sin reader: error explicito ──
68
+ const imgFail = await executeCoreFileRead({ absolute_path: '/fake/photo.png' });
69
+ assert.equal(imgFail.success, false);
70
+ assert.match(imgFail.error ?? '', /readImage/);
71
+ // ── PDF supported: usa readFull ──
72
+ const pdfFullCalls = [];
73
+ const pdfPagesCalls = [];
74
+ const pdfReader = {
75
+ isSupported: () => true,
76
+ getPageCount: async () => 5,
77
+ readFull: async (p) => {
78
+ pdfFullCalls.push(p);
79
+ return { type: 'pdf', file: { filePath: p, base64: 'AA', originalSize: 2 } };
80
+ },
81
+ extractPages: async (p, range) => {
82
+ pdfPagesCalls.push({ p, range });
83
+ return {
84
+ type: 'parts',
85
+ file: { filePath: p, outputDir: '/tmp', count: 1, originalSize: 2 },
86
+ };
87
+ },
88
+ };
89
+ const pdfResult = await executeCoreFileRead({ absolute_path: '/fake/doc.pdf' }, { pdfReader });
90
+ assert.equal(pdfResult.success, true);
91
+ assert.equal(pdfFullCalls.length, 1, 'supported PDF goes to readFull');
92
+ assert.equal(pdfPagesCalls.length, 0);
93
+ // ── PDF con pages: usa extractPages con rango parseado ──
94
+ const pdfRangeResult = await executeCoreFileRead({ absolute_path: '/fake/doc.pdf', pages: '1-3' }, { pdfReader });
95
+ assert.equal(pdfRangeResult.success, true);
96
+ assert.equal(pdfPagesCalls.length, 1, 'pages= triggers extractPages');
97
+ assert.deepEqual(pdfPagesCalls[0].range, { firstPage: 1, lastPage: 3 });
98
+ // ── PDF unsupported (modelo viejo): cae a extractPages sin rango ──
99
+ pdfPagesCalls.length = 0;
100
+ const unsupportedReader = {
101
+ ...pdfReader,
102
+ isSupported: () => false,
103
+ };
104
+ const pdfUnsupported = await executeCoreFileRead({ absolute_path: '/fake/doc2.pdf' }, { pdfReader: unsupportedReader });
105
+ assert.equal(pdfUnsupported.success, true);
106
+ assert.equal(pdfPagesCalls.length, 1, 'unsupported PDF falls back to extractPages');
107
+ assert.equal(pdfPagesCalls[0].range, undefined);
108
+ console.log('filesCore smoke PASS');
@@ -0,0 +1,20 @@
1
+ import { executeCoreShellCommand } from './shellCore.js';
2
+ export async function executeCoreGitCommand(input, context = {}) {
3
+ const command = input.command;
4
+ if (!command) {
5
+ return { success: false, error: 'git command is required' };
6
+ }
7
+ if (!command.trim().startsWith('git ')) {
8
+ return { success: false, error: 'Only git commands are allowed.' };
9
+ }
10
+ return executeCoreShellCommand({
11
+ ...input,
12
+ shellType: input.shellType ??
13
+ (process.platform === 'win32' ? 'powershell' : 'bash'),
14
+ }, context);
15
+ }
16
+ export const coreGitTool = {
17
+ name: 'Git',
18
+ description: 'Executes git commands within the workspace.',
19
+ execute: (input, context) => executeCoreGitCommand(input, context),
20
+ };
@@ -0,0 +1,36 @@
1
+ // Smoke E2E que valida que el orquestador del core preserva el shape
2
+ // retornado por el reader de imagen sin transformarlo. Para evitar ciclos
3
+ // de modulo, no importamos el adapter; usamos un reader stub que devuelve
4
+ // un shape representativo (mismo que produce readImageWithTokenBudget).
5
+ import assert from 'node:assert/strict';
6
+ import { executeCoreFileRead } from './filesCore.js';
7
+ const STUB_RESULT = {
8
+ type: 'image',
9
+ file: {
10
+ base64: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAA=',
11
+ type: 'image/png',
12
+ originalSize: 70,
13
+ dimensions: {
14
+ originalWidth: 1,
15
+ originalHeight: 1,
16
+ displayWidth: 1,
17
+ displayHeight: 1,
18
+ },
19
+ },
20
+ };
21
+ const calls = [];
22
+ const reader = async (p, mt, mb) => {
23
+ calls.push({ p, mt, mb });
24
+ return STUB_RESULT;
25
+ };
26
+ const result = await executeCoreFileRead({ absolute_path: '/fake/photo.png', max_tokens: 12345, max_bytes: 999 }, { readImage: reader });
27
+ assert.equal(result.success, true, 'success');
28
+ assert.equal(calls.length, 1, 'reader called exactly once');
29
+ assert.equal(calls[0].p, '/fake/photo.png', 'absolute_path forwarded');
30
+ assert.equal(calls[0].mt, 12345, 'max_tokens forwarded');
31
+ assert.equal(calls[0].mb, 999, 'max_bytes forwarded');
32
+ const data = result.data;
33
+ assert.equal(data.type, 'image', 'type preserved');
34
+ assert.deepEqual(data.file, STUB_RESULT.file, 'file shape preserved 1:1');
35
+ assert.equal(data.file.dimensions.originalWidth, STUB_RESULT.file.dimensions.originalWidth, 'dimensions preserved');
36
+ console.log('image-parity smoke PASS');
@@ -0,0 +1,68 @@
1
+ // Smoke E2E que valida que la lectura de notebook a traves del core
2
+ // (executeCoreFileRead con readNotebook inyectado) produce el mismo output
3
+ // que una lectura directa neutral del fixture.
4
+ //
5
+ // Este es el primer smoke de paridad ON vs OFF para tools migradas.
6
+ import assert from 'node:assert/strict';
7
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+ import { tmpdir } from 'node:os';
10
+ import { executeCoreFileRead } from './filesCore.js';
11
+ const NOTEBOOK_FIXTURE = {
12
+ cells: [
13
+ {
14
+ cell_type: 'code',
15
+ id: 'cell-1',
16
+ execution_count: 1,
17
+ metadata: {},
18
+ source: ['print("hello")\n'],
19
+ outputs: [
20
+ {
21
+ output_type: 'stream',
22
+ name: 'stdout',
23
+ text: ['hello\n'],
24
+ },
25
+ ],
26
+ },
27
+ {
28
+ cell_type: 'markdown',
29
+ id: 'cell-2',
30
+ metadata: {},
31
+ source: ['# Title\n', 'Some text\n'],
32
+ },
33
+ ],
34
+ metadata: {
35
+ language_info: { name: 'python' },
36
+ kernelspec: { name: 'python3', display_name: 'Python 3' },
37
+ },
38
+ nbformat: 4,
39
+ nbformat_minor: 5,
40
+ };
41
+ const tmp = mkdtempSync(join(tmpdir(), 'core-tools-nb-'));
42
+ try {
43
+ const nbPath = join(tmp, 'test.ipynb');
44
+ writeFileSync(nbPath, JSON.stringify(NOTEBOOK_FIXTURE), 'utf8');
45
+ // 1) Lectura directa (parser neutral del smoke)
46
+ const directParsed = JSON.parse(readFileSync(nbPath, 'utf8'));
47
+ const direct = Array.isArray(directParsed.cells) ? directParsed.cells : [];
48
+ assert.ok(Array.isArray(direct), 'direct read returns array');
49
+ assert.equal(direct.length, 2, 'two cells');
50
+ // 2) Lectura a traves del core con el mismo readNotebook inyectado
51
+ const coreResult = await executeCoreFileRead({ absolute_path: nbPath }, {
52
+ readNotebook: async (p) => {
53
+ const parsed = JSON.parse(readFileSync(p, 'utf8'));
54
+ return Array.isArray(parsed.cells) ? parsed.cells : [];
55
+ },
56
+ });
57
+ assert.equal(coreResult.success, true, 'core read success');
58
+ assert.ok(coreResult.data, 'core data exists');
59
+ const data = coreResult.data;
60
+ assert.equal(data.type, 'notebook');
61
+ assert.equal(data.file.cells.length, 2);
62
+ // 3) Las celulas son identicas (deep equal)
63
+ assert.deepEqual(data.file.cells, direct, 'core path produces identical cells to direct readNotebook');
64
+ }
65
+ finally {
66
+ rmSync(tmp, { recursive: true, force: true });
67
+ }
68
+ console.log('notebook-parity smoke PASS');
@@ -0,0 +1,22 @@
1
+ import { coreFileReadTool } from './filesCore.js';
2
+ import { coreGitTool } from './gitCore.js';
3
+ import { corePowerShellTool, coreShellTool } from './shellCore.js';
4
+ class Registry {
5
+ tools = new Map();
6
+ constructor() {
7
+ this.registerTool(coreFileReadTool);
8
+ this.registerTool(coreShellTool);
9
+ this.registerTool(corePowerShellTool);
10
+ this.registerTool(coreGitTool);
11
+ }
12
+ registerTool(tool) {
13
+ this.tools.set(tool.name, tool);
14
+ }
15
+ getTool(name) {
16
+ return this.tools.get(name);
17
+ }
18
+ listTools() {
19
+ return Array.from(this.tools.values());
20
+ }
21
+ }
22
+ export const coreToolsRegistry = new Registry();
@@ -0,0 +1,32 @@
1
+ import assert from 'node:assert/strict';
2
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { coreToolsRegistry } from './registry.js';
6
+ const tempDir = await mkdtemp(join(tmpdir(), 'core-tools-smoke-'));
7
+ try {
8
+ const fixture = join(tempDir, 'sample.txt');
9
+ await writeFile(fixture, ['alpha', 'beta', 'gamma'].join('\n'), 'utf8');
10
+ const readTool = coreToolsRegistry.getTool('Read');
11
+ assert.ok(readTool);
12
+ const readResult = await readTool.execute({ absolute_path: fixture, offset: 2, limit: 1 }, {});
13
+ assert.equal(readResult.success, true);
14
+ assert.equal(readResult.output, 'beta');
15
+ const shellTool = coreToolsRegistry.getTool(process.platform === 'win32' ? 'PowerShell' : 'Bash');
16
+ assert.ok(shellTool);
17
+ const shellResult = await shellTool.execute({
18
+ command: process.platform === 'win32'
19
+ ? 'Write-Output core-tools-smoke'
20
+ : 'printf core-tools-smoke',
21
+ }, {});
22
+ assert.equal(shellResult.success, true);
23
+ assert.match(shellResult.output ?? '', /core-tools-smoke/);
24
+ const gitTool = coreToolsRegistry.getTool('Git');
25
+ assert.ok(gitTool);
26
+ const gitResult = await gitTool.execute({ command: 'git status --short' }, { cwd: process.cwd() });
27
+ assert.equal(gitResult.success, true);
28
+ console.log('tools.runtime.smoke PASS');
29
+ }
30
+ finally {
31
+ await rm(tempDir, { recursive: true, force: true });
32
+ }
@@ -0,0 +1,60 @@
1
+ import { exec } from '../../utils/Shell.js';
2
+ function normalizeShellType(shellType) {
3
+ return shellType === 'powershell' ? 'powershell' : 'bash';
4
+ }
5
+ export async function startCoreShellCommand(input, context = {}) {
6
+ const command = input.command;
7
+ if (!command) {
8
+ throw new Error('command is required');
9
+ }
10
+ if (context.startShellCommand) {
11
+ return context.startShellCommand({
12
+ ...input,
13
+ shellType: normalizeShellType(input.shellType),
14
+ });
15
+ }
16
+ const controller = new AbortController();
17
+ if (context.abortSignal) {
18
+ const relayAbort = () => controller.abort(context.abortSignal?.reason);
19
+ if (context.abortSignal.aborted) {
20
+ relayAbort();
21
+ }
22
+ else {
23
+ context.abortSignal.addEventListener('abort', relayAbort, { once: true });
24
+ }
25
+ }
26
+ return exec(command, controller.signal, normalizeShellType(input.shellType), {
27
+ timeout: input.timeoutMs,
28
+ ...input.execOptions,
29
+ });
30
+ }
31
+ export async function executeCoreShellCommand(input, context = {}) {
32
+ try {
33
+ const shellCommand = await startCoreShellCommand(input, context);
34
+ const result = await shellCommand.result;
35
+ return formatCoreShellResult(result);
36
+ }
37
+ catch (err) {
38
+ return { success: false, error: String(err) };
39
+ }
40
+ }
41
+ function formatCoreShellResult(result) {
42
+ const output = [result.stdout, result.stderr].filter(Boolean).join('\n');
43
+ const isSuccess = result.code === 0 && !result.interrupted && !result.preSpawnError;
44
+ return {
45
+ success: isSuccess,
46
+ output: output || 'Command executed successfully with no output.',
47
+ error: isSuccess || !result.stderr ? undefined : result.stderr,
48
+ data: result,
49
+ };
50
+ }
51
+ export const coreShellTool = {
52
+ name: 'Bash',
53
+ description: 'Executes a shell command through the shared core runtime.',
54
+ execute: (input, context) => executeCoreShellCommand({ ...input, shellType: 'bash' }, context),
55
+ };
56
+ export const corePowerShellTool = {
57
+ name: 'PowerShell',
58
+ description: 'Executes a PowerShell command through the shared core runtime.',
59
+ execute: (input, context) => executeCoreShellCommand({ ...input, shellType: 'powershell' }, context),
60
+ };
@@ -0,0 +1,9 @@
1
+ // Tipos canonicos del core para AgentContext.
2
+ //
3
+ // Antes vivian en `utils/agentContext.ts`. Movidos aca para invertir la
4
+ // dependencia: el core es ahora el dueno del contrato; el adapter (utils)
5
+ // re-exporta para mantener compat de imports.
6
+ //
7
+ // Estos tipos son puros (no tienen dependencias del CLI). Se pueden usar
8
+ // desde el Desktop o cualquier otro embedder sin arrastrar Ink/REPL.
9
+ export {};
@@ -0,0 +1,3 @@
1
+ // Pure auth types — no runtime dependencies on CLI utilities.
2
+ // Owned by core. Used by loginCore.ts and logoutCore.ts.
3
+ export {};
@@ -0,0 +1,13 @@
1
+ // Pure command types — no runtime dependencies on CLI utilities.
2
+ // CLI-specific references (HookSettings, ToolUseContext, etc.) are
3
+ // simplified to `unknown` so this file is self-contained and embeddable.
4
+ /** Resolves the user-visible name, falling back to `cmd.name` when not overridden. */
5
+ export function getCommandName(cmd) {
6
+ if (!cmd)
7
+ return '';
8
+ return cmd.userFacingName?.() ?? cmd.name ?? '';
9
+ }
10
+ /** Resolves whether the command is enabled, defaulting to true. */
11
+ export function isCommandEnabled(cmd) {
12
+ return cmd.isEnabled?.() ?? true;
13
+ }
@@ -0,0 +1,3 @@
1
+ // Pure provider types - no runtime dependencies on CLI utilities.
2
+ // These are owned by core and can be used anywhere in the system.
3
+ export {};
@@ -0,0 +1,10 @@
1
+ // Tipos canonicos del core para SdkEvent.
2
+ //
3
+ // Antes vivian en `utils/sdkEventQueue.ts`. Movidos aca para invertir la
4
+ // dependencia: el core es ahora dueno del contrato; el adapter (utils)
5
+ // re-exporta para mantener compat de imports.
6
+ //
7
+ // `SdkWorkflowProgress` queda como tipo opaco (`unknown[]`) para no acoplar
8
+ // el core con el shape interno del CLI. El adapter puede tipar con mas
9
+ // detalle al consumir.
10
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -144,9 +144,9 @@ export function formatTotalCost() {
144
144
  : '');
145
145
  const modelUsageDisplay = formatModelUsage();
146
146
  return chalk.dim(`Costo total: ${costDisplay}\n` +
147
- `Duración total (API): ${formatDuration(getTotalAPIDuration())}
148
- Duración total (reloj): ${formatDuration(getTotalDuration())}
149
- Total cambios código: ${getTotalLinesAdded()} ${getTotalLinesAdded() === 1 ? 'línea' : 'líneas'} agregadas, ${getTotalLinesRemoved()} ${getTotalLinesRemoved() === 1 ? 'línea' : 'líneas'} eliminadas
147
+ `Duración total (API): ${formatDuration(getTotalAPIDuration())}
148
+ Duración total (reloj): ${formatDuration(getTotalDuration())}
149
+ Total cambios código: ${getTotalLinesAdded()} ${getTotalLinesAdded() === 1 ? 'línea' : 'líneas'} agregadas, ${getTotalLinesRemoved()} ${getTotalLinesRemoved() === 1 ? 'línea' : 'líneas'} eliminadas
150
150
  ${modelUsageDisplay}`);
151
151
  }
152
152
  function round(number, precision) {