codebase-onboarder 0.7.0 → 1.0.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 (128) hide show
  1. package/README.md +115 -12
  2. package/bin/onboarder.js +0 -0
  3. package/cli/agent/command.d.ts +71 -0
  4. package/cli/agent/command.js +177 -0
  5. package/cli/agent/command.js.map +1 -0
  6. package/cli/agent/config.d.ts +31 -0
  7. package/cli/agent/config.js +98 -0
  8. package/cli/agent/config.js.map +1 -0
  9. package/cli/agent/contracts.d.ts +77 -0
  10. package/cli/agent/contracts.js +2 -0
  11. package/cli/agent/contracts.js.map +1 -0
  12. package/cli/agent/files.d.ts +19 -0
  13. package/cli/agent/files.js +109 -0
  14. package/cli/agent/files.js.map +1 -0
  15. package/cli/agent/gitSource.d.ts +18 -0
  16. package/cli/agent/gitSource.js +69 -0
  17. package/cli/agent/gitSource.js.map +1 -0
  18. package/cli/agent/github.d.ts +21 -0
  19. package/cli/agent/github.js +94 -0
  20. package/cli/agent/github.js.map +1 -0
  21. package/cli/agent/mcp.d.ts +15 -0
  22. package/cli/agent/mcp.js +78 -0
  23. package/cli/agent/mcp.js.map +1 -0
  24. package/cli/agent/process.d.ts +18 -0
  25. package/cli/agent/process.js +118 -0
  26. package/cli/agent/process.js.map +1 -0
  27. package/cli/agent/protocol.d.ts +3 -0
  28. package/cli/agent/protocol.js +26 -0
  29. package/cli/agent/protocol.js.map +1 -0
  30. package/cli/agent/runner.d.ts +34 -0
  31. package/cli/agent/runner.js +152 -0
  32. package/cli/agent/runner.js.map +1 -0
  33. package/cli/agent/stdio.d.ts +1 -0
  34. package/cli/agent/stdio.js +62 -0
  35. package/cli/agent/stdio.js.map +1 -0
  36. package/cli/agent/tools.d.ts +7 -0
  37. package/cli/agent/tools.js +201 -0
  38. package/cli/agent/tools.js.map +1 -0
  39. package/cli/chat/app.d.ts +16 -0
  40. package/cli/chat/app.js +345 -0
  41. package/cli/chat/app.js.map +1 -0
  42. package/cli/chat/commands.d.ts +11 -0
  43. package/cli/chat/commands.js +120 -0
  44. package/cli/chat/commands.js.map +1 -0
  45. package/cli/chat/composer.d.ts +75 -0
  46. package/cli/chat/composer.js +467 -0
  47. package/cli/chat/composer.js.map +1 -0
  48. package/cli/chat/controller.d.ts +60 -0
  49. package/cli/chat/controller.js +393 -0
  50. package/cli/chat/controller.js.map +1 -0
  51. package/cli/chat/logo.d.ts +3 -0
  52. package/cli/chat/logo.js +14 -0
  53. package/cli/chat/logo.js.map +1 -0
  54. package/cli/chat/models.d.ts +18 -0
  55. package/cli/chat/models.js +88 -0
  56. package/cli/chat/models.js.map +1 -0
  57. package/cli/chat/profile.d.ts +5 -0
  58. package/cli/chat/profile.js +98 -0
  59. package/cli/chat/profile.js.map +1 -0
  60. package/cli/chat/render.d.ts +40 -0
  61. package/cli/chat/render.js +259 -0
  62. package/cli/chat/render.js.map +1 -0
  63. package/cli/chat/screen.d.ts +26 -0
  64. package/cli/chat/screen.js +117 -0
  65. package/cli/chat/screen.js.map +1 -0
  66. package/cli/chat/source.d.ts +29 -0
  67. package/cli/chat/source.js +116 -0
  68. package/cli/chat/source.js.map +1 -0
  69. package/cli/chat/store.d.ts +43 -0
  70. package/cli/chat/store.js +94 -0
  71. package/cli/chat/store.js.map +1 -0
  72. package/cli/chat/text.d.ts +4 -0
  73. package/cli/chat/text.js +27 -0
  74. package/cli/chat/text.js.map +1 -0
  75. package/cli/explorer/app.d.ts +1 -0
  76. package/cli/explorer/app.js +20 -1
  77. package/cli/explorer/app.js.map +1 -1
  78. package/cli/explorer/commands.js +5 -0
  79. package/cli/explorer/commands.js.map +1 -1
  80. package/cli/main.js +47 -13
  81. package/cli/main.js.map +1 -1
  82. package/docs/HERMES_RESEARCH.md +51 -0
  83. package/package.json +2 -1
  84. package/public/app.js +98 -24
  85. package/public/app.js.map +1 -1
  86. package/public/assets/onboarder-mark.svg +11 -0
  87. package/public/browser.d.ts +3 -0
  88. package/public/browser.js.map +1 -1
  89. package/public/index.html +24 -7
  90. package/public/js/about.js +2 -2
  91. package/public/js/about.js.map +1 -1
  92. package/public/js/aiDraft.d.ts +1 -0
  93. package/public/js/aiDraft.js +66 -17
  94. package/public/js/aiDraft.js.map +1 -1
  95. package/public/js/api.d.ts +8 -5
  96. package/public/js/api.js +45 -22
  97. package/public/js/api.js.map +1 -1
  98. package/public/js/deepAnalysisView.js +2 -2
  99. package/public/js/deepAnalysisView.js.map +1 -1
  100. package/public/js/diagramPane.d.ts +1 -0
  101. package/public/js/diagramPane.js +5 -0
  102. package/public/js/diagramPane.js.map +1 -1
  103. package/public/js/docsView.js +6 -6
  104. package/public/js/docsView.js.map +1 -1
  105. package/public/js/dom.d.ts +8 -0
  106. package/public/js/dom.js.map +1 -1
  107. package/public/js/llm.d.ts +3 -1
  108. package/public/js/llm.js +15 -8
  109. package/public/js/llm.js.map +1 -1
  110. package/public/js/repoFiles.js +1 -1
  111. package/public/js/repoFiles.js.map +1 -1
  112. package/public/js/reviewView.js +1 -1
  113. package/public/js/reviewView.js.map +1 -1
  114. package/public/styles.css +25 -0
  115. package/server/gitDiff.d.ts +1 -1
  116. package/server/hermesChat.d.ts +13 -0
  117. package/server/hermesChat.js +145 -0
  118. package/server/hermesChat.js.map +1 -0
  119. package/server/http.d.ts +5 -4
  120. package/server/http.js +0 -4
  121. package/server/http.js.map +1 -1
  122. package/server/mcp/server.d.ts +1 -1
  123. package/server/mcp/server.js +2 -2
  124. package/server/mcp/server.js.map +1 -1
  125. package/server/router.js +7 -3
  126. package/server/router.js.map +1 -1
  127. package/shared/diagram/aiMermaid.js +16 -13
  128. package/shared/diagram/aiMermaid.js.map +1 -1
@@ -0,0 +1,98 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import { createHash } from 'node:crypto';
3
+ import path from 'node:path';
4
+ import os from 'node:os';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { randomUUID } from 'node:crypto';
7
+ import { runProcess } from './process.js';
8
+ export const HERMES_SOURCE = 'https://github.com/NousResearch/hermes-agent.git';
9
+ export const HERMES_REVISION = 'eb7e8620324b32424c06218f6a28094df2e921f8';
10
+ export function agentHome(env = process.env) {
11
+ return path.resolve(env.ONBOARDER_AGENT_HOME || path.join(env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'onboarder', 'agent'));
12
+ }
13
+ export function hermesBinary(env = process.env) {
14
+ return env.ONBOARDER_HERMES_BIN || 'hermes';
15
+ }
16
+ export function runtimePaths(home = agentHome(), env = process.env) {
17
+ const native = process.platform === 'win32' ? path.join(env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'), 'hermes') : path.join(os.homedir(), '.hermes');
18
+ const configured = env.HERMES_HOME ? (path.basename(path.dirname(env.HERMES_HOME)) === 'profiles' ? path.dirname(path.dirname(env.HERMES_HOME)) : env.HERMES_HOME) : native;
19
+ // Named profiles share the installation's Python store, but not its config,
20
+ // credentials, sessions, memory, or MCP server selection.
21
+ const name = 'onboarder-' + createHash('sha256').update(path.resolve(home)).digest('hex').slice(0, 12);
22
+ return { home, profile: path.join(configured, 'profiles', name), runs: path.join(home, 'runs'), workspaces: path.join(home, 'workspaces'), server: fileURLToPath(new URL('./stdio.js', import.meta.url)) };
23
+ }
24
+ export function hermesEnvironment(home = agentHome(), env = process.env) {
25
+ return { ...env, HERMES_HOME: runtimePaths(home, env).profile, OPENAI_API_KEY: env.ONBOARDER_AI_API_KEY || env.OPENAI_API_KEY,
26
+ PATH: [path.join(os.homedir(), '.local', 'bin'), env.PATH || ''].join(path.delimiter) };
27
+ }
28
+ export async function setupAgent(home = agentHome(), env = process.env) {
29
+ const paths = runtimePaths(home, env);
30
+ for (const dir of [paths.profile, paths.runs, paths.workspaces])
31
+ await fs.mkdir(dir, { recursive: true, mode: 0o700 });
32
+ const file = path.join(paths.profile, 'config.yaml');
33
+ const base = env.ONBOARDER_AI_BASE_URL || env.OPENAI_BASE_URL;
34
+ const model = env.ONBOARDER_AI_MODEL || env.OPENAI_MODEL;
35
+ // JSON is a YAML subset. Hermes can subsequently maintain this file with its own model wizard.
36
+ const config = {
37
+ ...(base && model ? { model: { provider: 'custom', base_url: base, default: model } } : {}),
38
+ agent: { max_turns: 24 }, memory: { memory_enabled: false, user_profile_enabled: false },
39
+ tools: { tool_search: { enabled: 'off' } },
40
+ mcp_servers: { onboarder: { command: process.execPath, args: [paths.server],
41
+ env: { ONBOARDER_AGENT_RUN: '${ONBOARDER_AGENT_RUN}', GITHUB_TOKEN: '${GITHUB_TOKEN}', GH_TOKEN: '${GH_TOKEN}' },
42
+ tools: { prompts: false, resources: false }, connect_timeout: 30, timeout: 150 } },
43
+ };
44
+ let created = false;
45
+ try {
46
+ await fs.writeFile(file, JSON.stringify(config, null, 2) + '\n', { mode: 0o600, flag: 'wx' });
47
+ created = true;
48
+ }
49
+ catch (error) {
50
+ if (error.code !== 'EEXIST')
51
+ throw error;
52
+ }
53
+ return { ...paths, configFile: file, created };
54
+ }
55
+ /** Refresh only harness-owned settings; retain the model wizard's provider/auth choices. */
56
+ export async function prepareAgentProfile(home = agentHome(), env = process.env, signal) {
57
+ const setup = await setupAgent(home, env);
58
+ if (setup.created)
59
+ return setup;
60
+ const server = { command: process.execPath, args: [setup.server],
61
+ env: { ONBOARDER_AGENT_RUN: '${ONBOARDER_AGENT_RUN}', GITHUB_TOKEN: '${GITHUB_TOKEN}', GH_TOKEN: '${GH_TOKEN}' },
62
+ tools: { prompts: false, resources: false }, connect_timeout: 30, timeout: 150 };
63
+ const source = await fs.readFile(setup.configFile, 'utf8');
64
+ let config = null;
65
+ try {
66
+ const value = JSON.parse(source);
67
+ if (value && typeof value === 'object' && !Array.isArray(value))
68
+ config = value;
69
+ }
70
+ catch { /* Hermes' model wizard writes native YAML. */ }
71
+ if (config) {
72
+ config.mcp_servers = { ...(config.mcp_servers || {}), onboarder: server };
73
+ config.tools = { ...(config.tools || {}), tool_search: { ...(config.tools?.tool_search || {}), enabled: 'off' } };
74
+ config.memory = { ...(config.memory || {}), memory_enabled: false, user_profile_enabled: false };
75
+ const next = JSON.stringify(config, null, 2) + '\n';
76
+ if (source !== next) {
77
+ const temporary = setup.configFile + '-' + randomUUID();
78
+ try {
79
+ await fs.writeFile(temporary, next, { flag: 'wx', mode: 0o600 });
80
+ await fs.rename(temporary, setup.configFile);
81
+ }
82
+ finally {
83
+ await fs.rm(temporary, { force: true });
84
+ }
85
+ }
86
+ }
87
+ else {
88
+ // Let Hermes parse/preserve its own YAML rather than adding a Node dependency
89
+ // or hand-editing the model wizard's configuration.
90
+ for (const [key, value] of [['mcp_servers.onboarder', JSON.stringify(server)], ['tools.tool_search.enabled', 'off'], ['memory.memory_enabled', 'false'], ['memory.user_profile_enabled', 'false']]) {
91
+ const result = await runProcess(hermesBinary(env), ['config', 'set', key, value], { env: hermesEnvironment(home, env), signal, timeoutMs: 30_000, maxBytes: 128000 });
92
+ if (result.code)
93
+ throw new Error(`Could not refresh the dedicated Hermes profile: ${result.stderr.slice(-1500) || result.stdout.slice(-1500)}`);
94
+ }
95
+ }
96
+ return setup;
97
+ }
98
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/cli/agent/config.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs';\nimport { createHash } from 'node:crypto';\nimport path from 'node:path';\nimport os from 'node:os';\nimport { fileURLToPath } from 'node:url';\nimport { randomUUID } from 'node:crypto';\nimport { runProcess } from './process.js';\n\nexport const HERMES_SOURCE = 'https://github.com/NousResearch/hermes-agent.git';\nexport const HERMES_REVISION = 'eb7e8620324b32424c06218f6a28094df2e921f8';\nexport function agentHome(env: NodeJS.ProcessEnv = process.env): string {\n return path.resolve(env.ONBOARDER_AGENT_HOME || path.join(env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'onboarder', 'agent'));\n}\nexport function hermesBinary(env: NodeJS.ProcessEnv = process.env): string {\n return env.ONBOARDER_HERMES_BIN || 'hermes';\n}\nexport function runtimePaths(home = agentHome(), env: NodeJS.ProcessEnv = process.env) {\n const native = process.platform === 'win32' ? path.join(env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'), 'hermes') : path.join(os.homedir(), '.hermes');\n const configured = env.HERMES_HOME ? (path.basename(path.dirname(env.HERMES_HOME)) === 'profiles' ? path.dirname(path.dirname(env.HERMES_HOME)) : env.HERMES_HOME) : native;\n // Named profiles share the installation's Python store, but not its config,\n // credentials, sessions, memory, or MCP server selection.\n const name = 'onboarder-' + createHash('sha256').update(path.resolve(home)).digest('hex').slice(0, 12);\n return { home, profile: path.join(configured, 'profiles', name), runs: path.join(home, 'runs'), workspaces: path.join(home, 'workspaces'), server: fileURLToPath(new URL('./stdio.js', import.meta.url)) };\n}\nexport function hermesEnvironment(home = agentHome(), env: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {\n return { ...env, HERMES_HOME: runtimePaths(home, env).profile, OPENAI_API_KEY: env.ONBOARDER_AI_API_KEY || env.OPENAI_API_KEY,\n PATH: [path.join(os.homedir(), '.local', 'bin'), env.PATH || ''].join(path.delimiter) };\n}\nexport async function setupAgent(home = agentHome(), env: NodeJS.ProcessEnv = process.env) {\n const paths = runtimePaths(home, env);\n for (const dir of [paths.profile, paths.runs, paths.workspaces]) await fs.mkdir(dir, { recursive: true, mode: 0o700 });\n const file = path.join(paths.profile, 'config.yaml');\n const base = env.ONBOARDER_AI_BASE_URL || env.OPENAI_BASE_URL;\n const model = env.ONBOARDER_AI_MODEL || env.OPENAI_MODEL;\n // JSON is a YAML subset. Hermes can subsequently maintain this file with its own model wizard.\n const config = {\n ...(base && model ? { model: { provider: 'custom', base_url: base, default: model } } : {}),\n agent: { max_turns: 24 }, memory: { memory_enabled: false, user_profile_enabled: false },\n tools: { tool_search: { enabled: 'off' } },\n mcp_servers: { onboarder: { command: process.execPath, args: [paths.server],\n env: { ONBOARDER_AGENT_RUN: '${ONBOARDER_AGENT_RUN}', GITHUB_TOKEN: '${GITHUB_TOKEN}', GH_TOKEN: '${GH_TOKEN}' },\n tools: { prompts: false, resources: false }, connect_timeout: 30, timeout: 150 } },\n };\n let created = false;\n try { await fs.writeFile(file, JSON.stringify(config, null, 2) + '\\n', { mode: 0o600, flag: 'wx' }); created = true; }\n catch (error) { if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error; }\n return { ...paths, configFile: file, created };\n}\n\n/** Refresh only harness-owned settings; retain the model wizard's provider/auth choices. */\nexport async function prepareAgentProfile(home = agentHome(), env: NodeJS.ProcessEnv = process.env, signal?: AbortSignal) {\n const setup = await setupAgent(home, env);\n if (setup.created) return setup;\n const server = { command: process.execPath, args: [setup.server],\n env: { ONBOARDER_AGENT_RUN: '${ONBOARDER_AGENT_RUN}', GITHUB_TOKEN: '${GITHUB_TOKEN}', GH_TOKEN: '${GH_TOKEN}' },\n tools: { prompts: false, resources: false }, connect_timeout: 30, timeout: 150 };\n const source = await fs.readFile(setup.configFile, 'utf8');\n let config: Record<string, any> | null = null;\n try { const value = JSON.parse(source); if (value && typeof value === 'object' && !Array.isArray(value)) config = value; } catch { /* Hermes' model wizard writes native YAML. */ }\n if (config) {\n config.mcp_servers = { ...(config.mcp_servers || {}), onboarder: server };\n config.tools = { ...(config.tools || {}), tool_search: { ...(config.tools?.tool_search || {}), enabled: 'off' } };\n config.memory = { ...(config.memory || {}), memory_enabled: false, user_profile_enabled: false };\n const next = JSON.stringify(config, null, 2) + '\\n';\n if (source !== next) {\n const temporary = setup.configFile + '-' + randomUUID();\n try { await fs.writeFile(temporary, next, { flag: 'wx', mode: 0o600 }); await fs.rename(temporary, setup.configFile); }\n finally { await fs.rm(temporary, { force: true }); }\n }\n } else {\n // Let Hermes parse/preserve its own YAML rather than adding a Node dependency\n // or hand-editing the model wizard's configuration.\n for (const [key, value] of [['mcp_servers.onboarder', JSON.stringify(server)], ['tools.tool_search.enabled', 'off'], ['memory.memory_enabled', 'false'], ['memory.user_profile_enabled', 'false']]) {\n const result = await runProcess(hermesBinary(env), ['config', 'set', key, value], { env: hermesEnvironment(home, env), signal, timeoutMs: 30_000, maxBytes: 128000 });\n if (result.code) throw new Error(`Could not refresh the dedicated Hermes profile: ${result.stderr.slice(-1500) || result.stdout.slice(-1500)}`);\n }\n }\n return setup;\n}\n"]}
@@ -0,0 +1,77 @@
1
+ export declare const AGENT_MODES: readonly ['ask', 'review', 'triage', 'implement', 'pr', 'github'];
2
+ export type AgentMode = typeof AGENT_MODES[number];
3
+ export interface AgentRequest {
4
+ mode: AgentMode;
5
+ root: string;
6
+ task: string;
7
+ pr?: number;
8
+ issue?: number;
9
+ maxTurns?: number;
10
+ timeoutSeconds?: number;
11
+ allowChecks?: boolean;
12
+ allowGithubWrites?: boolean;
13
+ dryRun?: boolean;
14
+ model?: string;
15
+ provider?: string;
16
+ resume?: string;
17
+ base?: string;
18
+ }
19
+ export interface RunManifest {
20
+ schemaVersion: 1;
21
+ id: string;
22
+ mode: AgentMode;
23
+ sourceRoot: string;
24
+ root: string;
25
+ repository: string | null;
26
+ remote: string | null;
27
+ branch: string | null;
28
+ baseBranch: string | null;
29
+ task: string;
30
+ pr?: number;
31
+ issue?: number;
32
+ permissions: {
33
+ files: boolean;
34
+ checks: boolean;
35
+ github: boolean;
36
+ };
37
+ startedAt: string;
38
+ timeoutSeconds: number;
39
+ maxTurns: number;
40
+ auditFile: string;
41
+ }
42
+ export interface AgentEvent {
43
+ type: 'system' | 'text' | 'tool_use' | 'tool_result' | 'result';
44
+ subtype?: string;
45
+ name?: string;
46
+ text?: string;
47
+ session_id?: string;
48
+ exit_code?: number;
49
+ error?: string;
50
+ is_error?: boolean;
51
+ tokens?: Record<string, number>;
52
+ duration_ms?: number;
53
+ }
54
+ export interface AgentResult {
55
+ id: string;
56
+ status: 'completed' | 'failed' | 'cancelled';
57
+ mode: AgentMode;
58
+ answer: string;
59
+ sessionId: string | null;
60
+ workspace: string;
61
+ branch: string | null;
62
+ repository: string | null;
63
+ auditFile: string;
64
+ completedAt: string;
65
+ tokens?: Record<string, number>;
66
+ }
67
+ export interface AgentTool {
68
+ name: string;
69
+ description: string;
70
+ inputSchema: {
71
+ type: 'object';
72
+ properties: Record<string, unknown>;
73
+ required?: string[];
74
+ additionalProperties?: boolean;
75
+ };
76
+ run: (args: Record<string, unknown>) => Promise<unknown>;
77
+ }
@@ -0,0 +1,2 @@
1
+ export const AGENT_MODES = ['ask', 'review', 'triage', 'implement', 'pr', 'github'];
2
+ //# sourceMappingURL=contracts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contracts.js","sourceRoot":"","sources":["../../src/cli/agent/contracts.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,CAAU,CAAC","sourcesContent":["export const AGENT_MODES = ['ask', 'review', 'triage', 'implement', 'pr', 'github'] as const;\nexport type AgentMode = typeof AGENT_MODES[number];\nexport interface AgentRequest {\n mode: AgentMode;\n root: string;\n task: string;\n pr?: number;\n issue?: number;\n maxTurns?: number;\n timeoutSeconds?: number;\n allowChecks?: boolean;\n allowGithubWrites?: boolean;\n dryRun?: boolean;\n model?: string;\n provider?: string;\n resume?: string;\n base?: string;\n}\nexport interface RunManifest {\n schemaVersion: 1;\n id: string;\n mode: AgentMode;\n sourceRoot: string;\n root: string;\n repository: string | null;\n remote: string | null;\n branch: string | null;\n baseBranch: string | null;\n task: string;\n pr?: number;\n issue?: number;\n permissions: { files: boolean; checks: boolean; github: boolean };\n startedAt: string;\n timeoutSeconds: number;\n maxTurns: number;\n auditFile: string;\n}\nexport interface AgentEvent {\n type: 'system' | 'text' | 'tool_use' | 'tool_result' | 'result';\n subtype?: string;\n name?: string;\n text?: string;\n session_id?: string;\n exit_code?: number;\n error?: string;\n is_error?: boolean;\n tokens?: Record<string, number>;\n duration_ms?: number;\n}\nexport interface AgentResult {\n id: string;\n status: 'completed' | 'failed' | 'cancelled';\n mode: AgentMode;\n answer: string;\n sessionId: string | null;\n workspace: string;\n branch: string | null;\n repository: string | null;\n auditFile: string;\n completedAt: string;\n tokens?: Record<string, number>;\n}\nexport interface AgentTool {\n name: string;\n description: string;\n inputSchema: { type: 'object'; properties: Record<string, unknown>; required?: string[]; additionalProperties?: boolean };\n run: (args: Record<string, unknown>) => Promise<unknown>;\n}\n"]}
@@ -0,0 +1,19 @@
1
+ export declare const FILE_LIMIT: number;
2
+ export declare const hashText: (text: string) => string;
3
+ export declare function safeRelative(file: string): string;
4
+ export declare function scopedFile(root: string, file: string, createParents?: boolean): Promise<string>;
5
+ export declare function readAgentFile(root: string, file: string): Promise<{
6
+ file: string;
7
+ content: string;
8
+ sha256: string;
9
+ lines: number;
10
+ }>;
11
+ export declare function writeAgentFile(root: string, file: string, content: string, expected: string): Promise<{
12
+ file: string;
13
+ sha256: string;
14
+ bytes: number;
15
+ }>;
16
+ export declare function deleteAgentFile(root: string, file: string, expected: string): Promise<{
17
+ file: string;
18
+ deleted: boolean;
19
+ }>;
@@ -0,0 +1,109 @@
1
+ import { promises as fs, constants } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { createHash, randomUUID } from 'node:crypto';
4
+ export const FILE_LIMIT = 128 * 1024;
5
+ export const hashText = (text) => createHash('sha256').update(text).digest('hex');
6
+ export function safeRelative(file) {
7
+ if (!file || file.length > 500 || path.isAbsolute(file) || file.includes('\\') || /[\x00-\x1f]/.test(file))
8
+ throw new Error('Use a relative repository file path.');
9
+ const parts = file.split('/');
10
+ if (parts.some(p => !p || p === '..' || p === '.' || ['.git', 'node_modules', '.ssh', '.aws', '.npmrc', '.pypirc', '.netrc', '.git-credentials'].includes(p) || /^\.env(?:\.|$)/.test(p) || /^(?:id_rsa|id_ed25519)$|\.(?:pem|key)$/i.test(p)))
11
+ throw new Error('This path is outside the agent source-file scope.');
12
+ return file;
13
+ }
14
+ export async function scopedFile(root, file, createParents = false) {
15
+ safeRelative(file);
16
+ const canonical = await fs.realpath(root);
17
+ const parts = file.split('/');
18
+ let current = canonical;
19
+ for (let i = 0; i < parts.length; i++) {
20
+ current = path.join(current, parts[i]);
21
+ let stat;
22
+ try {
23
+ stat = await fs.lstat(current);
24
+ }
25
+ catch (e) {
26
+ if (e.code !== 'ENOENT')
27
+ throw e;
28
+ if (createParents && i < parts.length - 1)
29
+ await fs.mkdir(current);
30
+ else if (i < parts.length - 1)
31
+ throw new Error('The parent directory does not exist.');
32
+ continue;
33
+ }
34
+ if (stat.isSymbolicLink())
35
+ throw new Error('Agent file tools do not follow symbolic links.');
36
+ if (i < parts.length - 1 && !stat.isDirectory())
37
+ throw new Error('The parent path is not a directory.');
38
+ if (i === parts.length - 1 && !stat.isFile())
39
+ throw new Error('The path is not a regular file.');
40
+ }
41
+ return current;
42
+ }
43
+ async function readBounded(file) {
44
+ const handle = await fs.open(file, constants.O_RDONLY | (constants.O_NOFOLLOW || 0));
45
+ try {
46
+ const stat = await handle.stat();
47
+ if (!stat.isFile() || stat.size > FILE_LIMIT)
48
+ throw new Error(`File must be text and at most ${FILE_LIMIT} bytes.`);
49
+ const buffer = Buffer.alloc(FILE_LIMIT + 1);
50
+ let bytesRead = 0;
51
+ while (bytesRead < buffer.length) {
52
+ const read = await handle.read(buffer, bytesRead, buffer.length - bytesRead, bytesRead);
53
+ if (!read.bytesRead)
54
+ break;
55
+ bytesRead += read.bytesRead;
56
+ }
57
+ if (bytesRead > FILE_LIMIT || buffer.subarray(0, bytesRead).includes(0))
58
+ throw new Error('File exceeds the text-file limit or contains binary data.');
59
+ return buffer.subarray(0, bytesRead).toString('utf8');
60
+ }
61
+ finally {
62
+ await handle.close();
63
+ }
64
+ }
65
+ export async function readAgentFile(root, file) {
66
+ const content = await readBounded(await scopedFile(root, file));
67
+ return { file, content, sha256: hashText(content), lines: content.split('\n').length };
68
+ }
69
+ export async function writeAgentFile(root, file, content, expected) {
70
+ if (Buffer.byteLength(content) > FILE_LIMIT || content.includes('\0'))
71
+ throw new Error('New content exceeds the text-file limit.');
72
+ const target = await scopedFile(root, file, true);
73
+ const check = async () => {
74
+ try {
75
+ const current = await readBounded(target);
76
+ if (expected === 'new' || hashText(current) !== expected)
77
+ throw new Error('The file changed. Read it again before writing.');
78
+ }
79
+ catch (e) {
80
+ if (e.code === 'ENOENT' && expected === 'new')
81
+ return;
82
+ throw e;
83
+ }
84
+ };
85
+ await check();
86
+ const mode = await fs.stat(target).then(s => s.mode & 0o777).catch(() => 0o644);
87
+ const temporary = path.join(path.dirname(target), `.onboarder-${randomUUID()}.tmp`);
88
+ try {
89
+ await fs.writeFile(temporary, content, { mode, flag: 'wx' });
90
+ await check();
91
+ if (expected === 'new')
92
+ await fs.link(temporary, target);
93
+ else
94
+ await fs.rename(temporary, target);
95
+ }
96
+ finally {
97
+ await fs.unlink(temporary).catch(() => { });
98
+ }
99
+ return { file, sha256: hashText(content), bytes: Buffer.byteLength(content) };
100
+ }
101
+ export async function deleteAgentFile(root, file, expected) {
102
+ const target = await scopedFile(root, file);
103
+ const current = await readBounded(target);
104
+ if (hashText(current) !== expected)
105
+ throw new Error('The file changed. Read it again before deleting.');
106
+ await fs.unlink(target);
107
+ return { file, deleted: true };
108
+ }
109
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/cli/agent/files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAErD,MAAM,CAAC,MAAM,UAAU,GAAG,GAAG,GAAG,IAAI,CAAC;AACrC,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC1F,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IACpK,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,kBAAkB,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,yCAAyC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IACrT,OAAO,IAAI,CAAC;AACd,CAAC;AACD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY,EAAE,IAAY,EAAE,aAAa,GAAG,KAAK;IAChF,YAAY,CAAC,IAAI,CAAC,CAAC;IACnB,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,OAAO,GAAG,SAAS,CAAC;IACxB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACvC,IAAI,IAAI,CAAC;QACT,IAAI,CAAC;YAAC,IAAI,GAAG,MAAM,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAAC,CAAC;QACvC,OAAO,CAAC,EAAE,CAAC;YACT,IAAK,CAA2B,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,CAAC,CAAC;YAC5D,IAAI,aAAa,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC;gBAAE,MAAM,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;iBAC9D,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;YACvF,SAAS;QACX,CAAC;QACD,IAAI,IAAI,CAAC,cAAc,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;QAC7F,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;QACxG,IAAI,CAAC,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACnG,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AACD,KAAK,UAAU,WAAW,CAAC,IAAY;IACrC,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,QAAQ,GAAG,CAAC,SAAS,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,CAAC;IACrF,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QACjC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,IAAI,CAAC,IAAI,GAAG,UAAU;YAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,UAAU,SAAS,CAAC,CAAC;QACpH,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;QAC5C,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,OAAO,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;YACjC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,GAAG,SAAS,EAAE,SAAS,CAAC,CAAC;YACxF,IAAI,CAAC,IAAI,CAAC,SAAS;gBAAE,MAAM;YAC3B,SAAS,IAAI,IAAI,CAAC,SAAS,CAAC;QAC9B,CAAC;QACD,IAAI,SAAS,GAAG,UAAU,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,2DAA2D,CAAC,CAAC;QACtJ,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxD,CAAC;YAAS,CAAC;QAAC,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IAAC,CAAC;AACrC,CAAC;AACD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAY,EAAE,IAAY;IAC5D,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,MAAM,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IAChE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;AACzF,CAAC;AACD,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAY,EAAE,IAAY,EAAE,OAAe,EAAE,QAAgB;IAChG,IAAI,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,UAAU,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IACnI,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,KAAK,IAAI,EAAE;QACvB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;YAC1C,IAAI,QAAQ,KAAK,KAAK,IAAI,QAAQ,CAAC,OAAO,CAAC,KAAK,QAAQ;gBAAE,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;QAC/H,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAK,CAA2B,CAAC,IAAI,KAAK,QAAQ,IAAI,QAAQ,KAAK,KAAK;gBAAE,OAAO;YACjF,MAAM,CAAC,CAAC;QACV,CAAC;IACH,CAAC,CAAC;IACF,MAAM,KAAK,EAAE,CAAC;IACd,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;IAChF,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,cAAc,UAAU,EAAE,MAAM,CAAC,CAAC;IACpF,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,MAAM,KAAK,EAAE,CAAC;QACd,IAAI,QAAQ,KAAK,KAAK;YAAE,MAAM,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;;YACpD,MAAM,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAC1C,CAAC;YAAS,CAAC;QAAC,MAAM,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAAC,CAAC;IACzD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;AAChF,CAAC;AACD,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAY,EAAE,IAAY,EAAE,QAAgB;IAChF,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1C,IAAI,QAAQ,CAAC,OAAO,CAAC,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACxG,MAAM,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACxB,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AACjC,CAAC","sourcesContent":["import { promises as fs, constants } from 'node:fs';\nimport path from 'node:path';\nimport { createHash, randomUUID } from 'node:crypto';\n\nexport const FILE_LIMIT = 128 * 1024;\nexport const hashText = (text: string) => createHash('sha256').update(text).digest('hex');\nexport function safeRelative(file: string): string {\n if (!file || file.length > 500 || path.isAbsolute(file) || file.includes('\\\\') || /[\\x00-\\x1f]/.test(file)) throw new Error('Use a relative repository file path.');\n const parts = file.split('/');\n if (parts.some(p => !p || p === '..' || p === '.' || ['.git', 'node_modules', '.ssh', '.aws', '.npmrc', '.pypirc', '.netrc', '.git-credentials'].includes(p) || /^\\.env(?:\\.|$)/.test(p) || /^(?:id_rsa|id_ed25519)$|\\.(?:pem|key)$/i.test(p))) throw new Error('This path is outside the agent source-file scope.');\n return file;\n}\nexport async function scopedFile(root: string, file: string, createParents = false): Promise<string> {\n safeRelative(file);\n const canonical = await fs.realpath(root);\n const parts = file.split('/');\n let current = canonical;\n for (let i = 0; i < parts.length; i++) {\n current = path.join(current, parts[i]);\n let stat;\n try { stat = await fs.lstat(current); }\n catch (e) {\n if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw e;\n if (createParents && i < parts.length - 1) await fs.mkdir(current);\n else if (i < parts.length - 1) throw new Error('The parent directory does not exist.');\n continue;\n }\n if (stat.isSymbolicLink()) throw new Error('Agent file tools do not follow symbolic links.');\n if (i < parts.length - 1 && !stat.isDirectory()) throw new Error('The parent path is not a directory.');\n if (i === parts.length - 1 && !stat.isFile()) throw new Error('The path is not a regular file.');\n }\n return current;\n}\nasync function readBounded(file: string): Promise<string> {\n const handle = await fs.open(file, constants.O_RDONLY | (constants.O_NOFOLLOW || 0));\n try {\n const stat = await handle.stat();\n if (!stat.isFile() || stat.size > FILE_LIMIT) throw new Error(`File must be text and at most ${FILE_LIMIT} bytes.`);\n const buffer = Buffer.alloc(FILE_LIMIT + 1);\n let bytesRead = 0;\n while (bytesRead < buffer.length) {\n const read = await handle.read(buffer, bytesRead, buffer.length - bytesRead, bytesRead);\n if (!read.bytesRead) break;\n bytesRead += read.bytesRead;\n }\n if (bytesRead > FILE_LIMIT || buffer.subarray(0, bytesRead).includes(0)) throw new Error('File exceeds the text-file limit or contains binary data.');\n return buffer.subarray(0, bytesRead).toString('utf8');\n } finally { await handle.close(); }\n}\nexport async function readAgentFile(root: string, file: string) {\n const content = await readBounded(await scopedFile(root, file));\n return { file, content, sha256: hashText(content), lines: content.split('\\n').length };\n}\nexport async function writeAgentFile(root: string, file: string, content: string, expected: string) {\n if (Buffer.byteLength(content) > FILE_LIMIT || content.includes('\\0')) throw new Error('New content exceeds the text-file limit.');\n const target = await scopedFile(root, file, true);\n const check = async () => {\n try {\n const current = await readBounded(target);\n if (expected === 'new' || hashText(current) !== expected) throw new Error('The file changed. Read it again before writing.');\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT' && expected === 'new') return;\n throw e;\n }\n };\n await check();\n const mode = await fs.stat(target).then(s => s.mode & 0o777).catch(() => 0o644);\n const temporary = path.join(path.dirname(target), `.onboarder-${randomUUID()}.tmp`);\n try {\n await fs.writeFile(temporary, content, { mode, flag: 'wx' });\n await check();\n if (expected === 'new') await fs.link(temporary, target);\n else await fs.rename(temporary, target);\n } finally { await fs.unlink(temporary).catch(() => {}); }\n return { file, sha256: hashText(content), bytes: Buffer.byteLength(content) };\n}\nexport async function deleteAgentFile(root: string, file: string, expected: string) {\n const target = await scopedFile(root, file);\n const current = await readBounded(target);\n if (hashText(current) !== expected) throw new Error('The file changed. Read it again before deleting.');\n await fs.unlink(target);\n return { file, deleted: true };\n}\n"]}
@@ -0,0 +1,18 @@
1
+ import { GithubClient } from './github.js';
2
+ /** Resolve immutable Git objects, checking modes before reading any blob. The
3
+ * contents API can dereference symlinks, so it cannot enforce our file scope. */
4
+ export declare class GithubSourceReader {
5
+ private client;
6
+ private roots;
7
+ private trees;
8
+ constructor(client: GithubClient);
9
+ read(file: string, sha: string): Promise<{
10
+ repository: string;
11
+ file: string;
12
+ commitSha: string;
13
+ blobSha: string;
14
+ content: string;
15
+ sha256: string;
16
+ bytes: number;
17
+ }>;
18
+ }
@@ -0,0 +1,69 @@
1
+ import { GithubClient } from './github.js';
2
+ import { safeRelative, FILE_LIMIT, hashText } from './files.js';
3
+ const validSha = (sha) => typeof sha === 'string' && /^[a-f\d]{40,64}$/i.test(sha);
4
+ /** Resolve immutable Git objects, checking modes before reading any blob. The
5
+ * contents API can dereference symlinks, so it cannot enforce our file scope. */
6
+ export class GithubSourceReader {
7
+ client;
8
+ roots = new Map();
9
+ trees = new Map();
10
+ constructor(client) {
11
+ this.client = client;
12
+ }
13
+ async read(file, sha) {
14
+ safeRelative(file);
15
+ if (!validSha(sha))
16
+ throw new Error('Use an exact commit SHA from the PR or GitHub run.');
17
+ const parts = file.split('/');
18
+ if (parts.length > 20)
19
+ throw new Error('The source path exceeds the directory-depth limit.');
20
+ let tree = this.roots.get(sha);
21
+ if (!tree) {
22
+ const commit = (await this.client.request(`/git/commits/${sha}`)).data;
23
+ tree = commit?.tree?.sha;
24
+ if (!validSha(tree))
25
+ throw new Error('GitHub returned no valid commit tree.');
26
+ if (this.roots.size >= 64)
27
+ this.roots.clear();
28
+ this.roots.set(sha, tree);
29
+ }
30
+ let entry;
31
+ for (let i = 0; i < parts.length; i++) {
32
+ let entries = this.trees.get(tree);
33
+ if (!entries) {
34
+ const response = (await this.client.request(`/git/trees/${tree}`)).data;
35
+ if (!response || !Array.isArray(response.tree) || response.truncated)
36
+ throw new Error('GitHub tree coverage is incomplete.');
37
+ entries = response.tree;
38
+ if (this.trees.size >= 32)
39
+ this.trees.clear();
40
+ this.trees.set(tree, entries);
41
+ }
42
+ entry = entries.find(e => e.path === parts[i]);
43
+ if (!entry || !validSha(entry.sha))
44
+ throw new Error('Source file is not present at that commit.');
45
+ if (i < parts.length - 1) {
46
+ if (entry.type !== 'tree' || entry.mode !== '040000')
47
+ throw new Error('Source paths cannot traverse symlinks or submodules.');
48
+ tree = entry.sha;
49
+ }
50
+ }
51
+ if (!entry || entry.type !== 'blob' || !['100644', '100755'].includes(entry.mode) || !Number.isSafeInteger(entry.size) || entry.size < 0 || entry.size > FILE_LIMIT)
52
+ throw new Error('Source must be a regular text file, not a symlink or submodule, at most 128 KiB.');
53
+ const response = (await this.client.request(`/git/blobs/${entry.sha}`)).data;
54
+ if (!response || response.encoding !== 'base64' || typeof response.content !== 'string' || response.size !== entry.size)
55
+ throw new Error('GitHub returned an invalid source blob.');
56
+ const buffer = Buffer.from(response.content, 'base64');
57
+ if (buffer.length !== entry.size || buffer.length > FILE_LIMIT || buffer.includes(0))
58
+ throw new Error('GitHub file exceeds the text limit or contains binary data.');
59
+ let content;
60
+ try {
61
+ content = new TextDecoder('utf-8', { fatal: true }).decode(buffer);
62
+ }
63
+ catch {
64
+ throw new Error('GitHub file is not UTF-8 source text.');
65
+ }
66
+ return { repository: this.client.repository, file, commitSha: sha, blobSha: entry.sha, content, sha256: hashText(content), bytes: buffer.length };
67
+ }
68
+ }
69
+ //# sourceMappingURL=gitSource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitSource.js","sourceRoot":"","sources":["../../src/cli/agent/gitSource.ts"],"names":[],"mappings":"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","sourcesContent":["import { GithubClient } from './github.js';\nimport { safeRelative, FILE_LIMIT, hashText } from './files.js';\n\ntype Entry = { path: string; mode: string; type: string; sha: string; size?: number };\nconst validSha = (sha: unknown): sha is string => typeof sha === 'string' && /^[a-f\\d]{40,64}$/i.test(sha);\n/** Resolve immutable Git objects, checking modes before reading any blob. The\n * contents API can dereference symlinks, so it cannot enforce our file scope. */\nexport class GithubSourceReader {\n private roots = new Map<string, string>();\n private trees = new Map<string, Entry[]>();\n constructor(private client: GithubClient) {}\n async read(file: string, sha: string) {\n safeRelative(file);\n if (!validSha(sha)) throw new Error('Use an exact commit SHA from the PR or GitHub run.');\n const parts = file.split('/');\n if (parts.length > 20) throw new Error('The source path exceeds the directory-depth limit.');\n let tree = this.roots.get(sha);\n if (!tree) {\n const commit = (await this.client.request(`/git/commits/${sha}`)).data as { tree?: { sha?: string } } | null;\n tree = commit?.tree?.sha;\n if (!validSha(tree)) throw new Error('GitHub returned no valid commit tree.');\n if (this.roots.size >= 64) this.roots.clear(); this.roots.set(sha, tree);\n }\n let entry: Entry | undefined;\n for (let i = 0; i < parts.length; i++) {\n let entries = this.trees.get(tree);\n if (!entries) {\n const response = (await this.client.request(`/git/trees/${tree}`)).data as { tree?: Entry[]; truncated?: boolean } | null;\n if (!response || !Array.isArray(response.tree) || response.truncated) throw new Error('GitHub tree coverage is incomplete.');\n entries = response.tree;\n if (this.trees.size >= 32) this.trees.clear(); this.trees.set(tree, entries);\n }\n entry = entries.find(e => e.path === parts[i]);\n if (!entry || !validSha(entry.sha)) throw new Error('Source file is not present at that commit.');\n if (i < parts.length - 1) {\n if (entry.type !== 'tree' || entry.mode !== '040000') throw new Error('Source paths cannot traverse symlinks or submodules.');\n tree = entry.sha;\n }\n }\n if (!entry || entry.type !== 'blob' || !['100644', '100755'].includes(entry.mode) || !Number.isSafeInteger(entry.size) || entry.size! < 0 || entry.size! > FILE_LIMIT) throw new Error('Source must be a regular text file, not a symlink or submodule, at most 128 KiB.');\n const response = (await this.client.request(`/git/blobs/${entry.sha}`)).data as { encoding?: string; content?: string; size?: number } | null;\n if (!response || response.encoding !== 'base64' || typeof response.content !== 'string' || response.size !== entry.size) throw new Error('GitHub returned an invalid source blob.');\n const buffer = Buffer.from(response.content, 'base64');\n if (buffer.length !== entry.size || buffer.length > FILE_LIMIT || buffer.includes(0)) throw new Error('GitHub file exceeds the text limit or contains binary data.');\n let content: string;\n try { content = new TextDecoder('utf-8', { fatal: true }).decode(buffer); } catch { throw new Error('GitHub file is not UTF-8 source text.'); }\n return { repository: this.client.repository, file, commitSha: sha, blobSha: entry.sha, content, sha256: hashText(content), bytes: buffer.length };\n }\n}\n"]}
@@ -0,0 +1,21 @@
1
+ export interface GithubOptions {
2
+ token?: string;
3
+ fetchImpl?: typeof fetch;
4
+ signal?: AbortSignal;
5
+ }
6
+ export declare function githubToken(env?: NodeJS.ProcessEnv): string;
7
+ export declare function repositoryFromRemote(remote: string): string | null;
8
+ export declare function positiveId(value: unknown, label: string): number;
9
+ export declare class GithubClient {
10
+ readonly repository: string;
11
+ private options;
12
+ constructor(repository: string, options?: GithubOptions);
13
+ request(route: string, method?: string, body?: unknown): Promise<{
14
+ data: unknown;
15
+ more: boolean;
16
+ }>;
17
+ list(route: string, pages?: number): Promise<{
18
+ items: unknown[];
19
+ truncated: boolean;
20
+ }>;
21
+ }
@@ -0,0 +1,94 @@
1
+ export function githubToken(env = process.env) {
2
+ return [env.GITHUB_TOKEN, env.GH_TOKEN].map(t => (t || '').trim()).find(t => t && !t.startsWith('${')) || '';
3
+ }
4
+ export function repositoryFromRemote(remote) {
5
+ const match = remote.match(/^(?:https:\/\/github\.com\/|git@github\.com:|ssh:\/\/git@github\.com\/)([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git)?\/?$/i);
6
+ return match && validRepository(`${match[1]}/${match[2]}`) ? `${match[1]}/${match[2]}` : null;
7
+ }
8
+ function validRepository(value) {
9
+ const [owner, name, extra] = value.split('/');
10
+ return !extra && /^[A-Za-z0-9][A-Za-z0-9-]{0,38}$/.test(owner || '') && /^[A-Za-z0-9_.-]{1,100}$/.test(name || '') && name !== '.' && name !== '..';
11
+ }
12
+ export function positiveId(value, label) {
13
+ if (typeof value !== 'number' && (typeof value !== 'string' || !/^\d+$/.test(value)))
14
+ throw new Error(`${label} must be a positive integer.`);
15
+ const id = typeof value === 'number' ? value : Number(value);
16
+ if (!Number.isSafeInteger(id) || id < 1 || id > 2147483647)
17
+ throw new Error(`${label} must be a positive integer.`);
18
+ return id;
19
+ }
20
+ export class GithubClient {
21
+ repository;
22
+ options;
23
+ constructor(repository, options = {}) {
24
+ this.repository = repository;
25
+ this.options = options;
26
+ if (!validRepository(repository))
27
+ throw new Error('Use a GitHub repository in owner/name form.');
28
+ }
29
+ async request(route, method = 'GET', body) {
30
+ if ((route !== '' && !route.startsWith('/')) || route.includes('..') || route.includes('#') || route.includes('\\') || /%(?:2e|2f|5c)/i.test(route.split('?')[0]))
31
+ throw new Error('Invalid GitHub route.');
32
+ const headers = { accept: 'application/vnd.github+json', 'x-github-api-version': '2022-11-28', 'user-agent': 'onboarder-hermes' };
33
+ const token = this.options.token ?? githubToken();
34
+ if (token)
35
+ headers.authorization = `Bearer ${token}`;
36
+ if (body !== undefined)
37
+ headers['content-type'] = 'application/json';
38
+ const signal = this.options.signal ? AbortSignal.any([this.options.signal, AbortSignal.timeout(15_000)]) : AbortSignal.timeout(15_000);
39
+ const res = await (this.options.fetchImpl || fetch)(`https://api.github.com/repos/${this.repository}${route}`, {
40
+ method, headers, signal, redirect: 'error', ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
41
+ });
42
+ if (!res.ok) {
43
+ const reasons = { 401: 'GitHub authentication failed. Set GITHUB_TOKEN or GH_TOKEN.', 403: 'GitHub denied the request or its rate limit was reached.', 404: 'GitHub could not find this repository or resource; private repositories require a token.', 422: 'GitHub rejected the request. Check the issue, branch, or pull-request fields.' };
44
+ throw new Error(reasons[res.status] || `GitHub returned ${res.status}.`);
45
+ }
46
+ if (Number(res.headers.get('content-length')) > 2 * 1024 * 1024)
47
+ throw new Error('GitHub response exceeds the size limit.');
48
+ if (res.status === 204)
49
+ return { data: null, more: false };
50
+ const reader = res.body?.getReader();
51
+ let bytes = 0, text = '';
52
+ const decoder = new TextDecoder();
53
+ if (reader) {
54
+ try {
55
+ for (;;) {
56
+ const { done, value } = await reader.read();
57
+ if (done)
58
+ break;
59
+ bytes += value.byteLength;
60
+ if (bytes > 2 * 1024 * 1024)
61
+ throw new Error('GitHub response exceeds the size limit.');
62
+ text += decoder.decode(value, { stream: true });
63
+ }
64
+ text += decoder.decode();
65
+ }
66
+ finally {
67
+ await reader.cancel().catch(() => { });
68
+ }
69
+ }
70
+ let data;
71
+ try {
72
+ data = JSON.parse(text);
73
+ }
74
+ catch {
75
+ throw new Error('GitHub returned an unreadable response.');
76
+ }
77
+ return { data, more: /rel="next"/.test(res.headers.get('link') || '') };
78
+ }
79
+ async list(route, pages = 2) {
80
+ const items = [];
81
+ let more = false;
82
+ for (let page = 1; page <= pages; page++) {
83
+ const response = await this.request(`${route}${route.includes('?') ? '&' : '?'}per_page=100&page=${page}`);
84
+ if (!Array.isArray(response.data))
85
+ throw new Error('GitHub returned an unreadable list.');
86
+ items.push(...response.data);
87
+ more = response.more;
88
+ if (!more)
89
+ break;
90
+ }
91
+ return { items, truncated: more };
92
+ }
93
+ }
94
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/cli/agent/github.ts"],"names":[],"mappings":"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","sourcesContent":["export interface GithubOptions { token?: string; fetchImpl?: typeof fetch; signal?: AbortSignal }\nexport function githubToken(env: NodeJS.ProcessEnv = process.env): string {\n return [env.GITHUB_TOKEN, env.GH_TOKEN].map(t => (t || '').trim()).find(t => t && !t.startsWith('${')) || '';\n}\nexport function repositoryFromRemote(remote: string): string | null {\n const match = remote.match(/^(?:https:\\/\\/github\\.com\\/|git@github\\.com:|ssh:\\/\\/git@github\\.com\\/)([A-Za-z0-9_.-]+)\\/([A-Za-z0-9_.-]+?)(?:\\.git)?\\/?$/i);\n return match && validRepository(`${match[1]}/${match[2]}`) ? `${match[1]}/${match[2]}` : null;\n}\nfunction validRepository(value: string): boolean {\n const [owner, name, extra] = value.split('/');\n return !extra && /^[A-Za-z0-9][A-Za-z0-9-]{0,38}$/.test(owner || '') && /^[A-Za-z0-9_.-]{1,100}$/.test(name || '') && name !== '.' && name !== '..';\n}\nexport function positiveId(value: unknown, label: string): number {\n if (typeof value !== 'number' && (typeof value !== 'string' || !/^\\d+$/.test(value))) throw new Error(`${label} must be a positive integer.`);\n const id = typeof value === 'number' ? value : Number(value);\n if (!Number.isSafeInteger(id) || id < 1 || id > 2147483647) throw new Error(`${label} must be a positive integer.`);\n return id;\n}\nexport class GithubClient {\n constructor(readonly repository: string, private options: GithubOptions = {}) {\n if (!validRepository(repository)) throw new Error('Use a GitHub repository in owner/name form.');\n }\n async request(route: string, method = 'GET', body?: unknown): Promise<{ data: unknown; more: boolean }> {\n if ((route !== '' && !route.startsWith('/')) || route.includes('..') || route.includes('#') || route.includes('\\\\') || /%(?:2e|2f|5c)/i.test(route.split('?')[0])) throw new Error('Invalid GitHub route.');\n const headers: Record<string, string> = { accept: 'application/vnd.github+json', 'x-github-api-version': '2022-11-28', 'user-agent': 'onboarder-hermes' };\n const token = this.options.token ?? githubToken();\n if (token) headers.authorization = `Bearer ${token}`;\n if (body !== undefined) headers['content-type'] = 'application/json';\n const signal = this.options.signal ? AbortSignal.any([this.options.signal, AbortSignal.timeout(15_000)]) : AbortSignal.timeout(15_000);\n const res = await (this.options.fetchImpl || fetch)(`https://api.github.com/repos/${this.repository}${route}`, {\n method, headers, signal, redirect: 'error', ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n });\n if (!res.ok) {\n const reasons: Record<number, string> = { 401: 'GitHub authentication failed. Set GITHUB_TOKEN or GH_TOKEN.', 403: 'GitHub denied the request or its rate limit was reached.', 404: 'GitHub could not find this repository or resource; private repositories require a token.', 422: 'GitHub rejected the request. Check the issue, branch, or pull-request fields.' };\n throw new Error(reasons[res.status] || `GitHub returned ${res.status}.`);\n }\n if (Number(res.headers.get('content-length')) > 2 * 1024 * 1024) throw new Error('GitHub response exceeds the size limit.');\n if (res.status === 204) return { data: null, more: false };\n const reader = res.body?.getReader();\n let bytes = 0, text = '';\n const decoder = new TextDecoder();\n if (reader) {\n try { for (;;) { const { done, value } = await reader.read(); if (done) break; bytes += value.byteLength;\n if (bytes > 2 * 1024 * 1024) throw new Error('GitHub response exceeds the size limit.'); text += decoder.decode(value, { stream: true }); } text += decoder.decode(); }\n finally { await reader.cancel().catch(() => {}); }\n }\n let data: unknown;\n try { data = JSON.parse(text); } catch { throw new Error('GitHub returned an unreadable response.'); }\n return { data, more: /rel=\"next\"/.test(res.headers.get('link') || '') };\n }\n async list(route: string, pages = 2): Promise<{ items: unknown[]; truncated: boolean }> {\n const items: unknown[] = []; let more = false;\n for (let page = 1; page <= pages; page++) {\n const response = await this.request(`${route}${route.includes('?') ? '&' : '?'}per_page=100&page=${page}`);\n if (!Array.isArray(response.data)) throw new Error('GitHub returned an unreadable list.');\n items.push(...response.data); more = response.more; if (!more) break;\n }\n return { items, truncated: more };\n }\n}\n"]}
@@ -0,0 +1,15 @@
1
+ import { createAgentTools } from './tools.js';
2
+ import type { AgentTool, RunManifest } from './contracts.js';
3
+ export declare function scopedTools(manifest: RunManifest, options?: Parameters<typeof createAgentTools>[1]): AgentTool[];
4
+ export declare function createAgentDispatcher(manifest: RunManifest, options?: Parameters<typeof createAgentTools>[1]): (msg: any) => Promise<{
5
+ jsonrpc: string;
6
+ id: any;
7
+ error: {
8
+ code: number;
9
+ message: string;
10
+ };
11
+ } | {
12
+ jsonrpc: string;
13
+ id: any;
14
+ result: unknown;
15
+ }>;