@kolisachint/hoocode-agent 0.5.21 → 0.5.23

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 (90) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/config.d.ts +11 -0
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +18 -0
  5. package/dist/config.js.map +1 -1
  6. package/dist/core/agent-log.d.ts +44 -0
  7. package/dist/core/agent-log.d.ts.map +1 -0
  8. package/dist/core/agent-log.js +74 -0
  9. package/dist/core/agent-log.js.map +1 -0
  10. package/dist/core/canvas/discovery.d.ts +60 -0
  11. package/dist/core/canvas/discovery.d.ts.map +1 -0
  12. package/dist/core/canvas/discovery.js +83 -0
  13. package/dist/core/canvas/discovery.js.map +1 -0
  14. package/dist/core/canvas/launch.d.ts +90 -0
  15. package/dist/core/canvas/launch.d.ts.map +1 -0
  16. package/dist/core/canvas/launch.js +176 -0
  17. package/dist/core/canvas/launch.js.map +1 -0
  18. package/dist/core/canvas/protocol.d.ts +229 -0
  19. package/dist/core/canvas/protocol.d.ts.map +1 -0
  20. package/dist/core/canvas/protocol.js +129 -0
  21. package/dist/core/canvas/protocol.js.map +1 -0
  22. package/dist/core/canvas/registry.d.ts +139 -0
  23. package/dist/core/canvas/registry.d.ts.map +1 -0
  24. package/dist/core/canvas/registry.js +275 -0
  25. package/dist/core/canvas/registry.js.map +1 -0
  26. package/dist/core/canvas/resolver.d.ts +26 -0
  27. package/dist/core/canvas/resolver.d.ts.map +1 -0
  28. package/dist/core/canvas/resolver.js +57 -0
  29. package/dist/core/canvas/resolver.js.map +1 -0
  30. package/dist/core/canvas/runner.d.ts +101 -0
  31. package/dist/core/canvas/runner.d.ts.map +1 -0
  32. package/dist/core/canvas/runner.js +196 -0
  33. package/dist/core/canvas/runner.js.map +1 -0
  34. package/dist/core/canvas/sdk-shim/dispatch.test-helpers.d.ts +23 -0
  35. package/dist/core/canvas/sdk-shim/dispatch.test-helpers.d.ts.map +1 -0
  36. package/dist/core/canvas/sdk-shim/dispatch.test-helpers.js +36 -0
  37. package/dist/core/canvas/sdk-shim/dispatch.test-helpers.js.map +1 -0
  38. package/dist/core/canvas/sdk-shim/index.d.ts +113 -0
  39. package/dist/core/canvas/sdk-shim/index.d.ts.map +1 -0
  40. package/dist/core/canvas/sdk-shim/index.js +184 -0
  41. package/dist/core/canvas/sdk-shim/index.js.map +1 -0
  42. package/dist/core/canvas/session.d.ts +112 -0
  43. package/dist/core/canvas/session.d.ts.map +1 -0
  44. package/dist/core/canvas/session.js +197 -0
  45. package/dist/core/canvas/session.js.map +1 -0
  46. package/dist/core/canvas/trust.d.ts +71 -0
  47. package/dist/core/canvas/trust.d.ts.map +1 -0
  48. package/dist/core/canvas/trust.js +88 -0
  49. package/dist/core/canvas/trust.js.map +1 -0
  50. package/dist/core/extensions/loader.d.ts.map +1 -1
  51. package/dist/core/extensions/loader.js +7 -10
  52. package/dist/core/extensions/loader.js.map +1 -1
  53. package/dist/core/extensions/plugins/trust.d.ts +21 -0
  54. package/dist/core/extensions/plugins/trust.d.ts.map +1 -1
  55. package/dist/core/extensions/plugins/trust.js +26 -0
  56. package/dist/core/extensions/plugins/trust.js.map +1 -1
  57. package/dist/core/lifeguard.d.ts.map +1 -1
  58. package/dist/core/lifeguard.js +2 -1
  59. package/dist/core/lifeguard.js.map +1 -1
  60. package/dist/core/subagent-pool.d.ts.map +1 -1
  61. package/dist/core/subagent-pool.js +6 -4
  62. package/dist/core/subagent-pool.js.map +1 -1
  63. package/dist/core/tools/canvas.d.ts +55 -0
  64. package/dist/core/tools/canvas.d.ts.map +1 -0
  65. package/dist/core/tools/canvas.js +159 -0
  66. package/dist/core/tools/canvas.js.map +1 -0
  67. package/dist/core/tools/subagent.d.ts.map +1 -1
  68. package/dist/core/tools/subagent.js +3 -2
  69. package/dist/core/tools/subagent.js.map +1 -1
  70. package/dist/extensions/core/canvas.d.ts +20 -0
  71. package/dist/extensions/core/canvas.d.ts.map +1 -0
  72. package/dist/extensions/core/canvas.js +192 -0
  73. package/dist/extensions/core/canvas.js.map +1 -0
  74. package/dist/extensions/core/hoo-core.d.ts +1 -0
  75. package/dist/extensions/core/hoo-core.d.ts.map +1 -1
  76. package/dist/extensions/core/hoo-core.js +3 -0
  77. package/dist/extensions/core/hoo-core.js.map +1 -1
  78. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  79. package/dist/modes/interactive/interactive-mode.js +7 -0
  80. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  81. package/dist/utils/paths.d.ts +9 -0
  82. package/dist/utils/paths.d.ts.map +1 -1
  83. package/dist/utils/paths.js +16 -0
  84. package/dist/utils/paths.js.map +1 -1
  85. package/docs/routing.md +1 -1
  86. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  87. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  88. package/examples/extensions/sandbox/package.json +1 -1
  89. package/examples/extensions/with-deps/package.json +1 -1
  90. package/package.json +5 -4
@@ -5,6 +5,15 @@
5
5
  * entries.
6
6
  */
7
7
  export declare function canonicalizePath(path: string): string;
8
+ /**
9
+ * Whether `target` is `root` itself or sits inside it.
10
+ *
11
+ * Segment-aware, so `/a/bc` is not treated as being inside `/a/b`. Four private
12
+ * copies of this predicate already exist (`core/extensions/loader.ts`,
13
+ * `core/extensions/plugins/install.ts`, `core/skills.ts`,
14
+ * `core/prompt-templates.ts`); new callers should use this one.
15
+ */
16
+ export declare function isPathInside(target: string, root: string): boolean;
8
17
  /**
9
18
  * Returns true if the value is NOT a package source (npm:, git:, etc.)
10
19
  * or a URL protocol. Bare names and relative paths without ./ prefix
@@ -1 +1 @@
1
- {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMrD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAclD;AAMD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CASpF;AAED,wBAAgB,iCAAiC,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGvF;AAaD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAapF","sourcesContent":["import { existsSync, realpathSync } from \"node:fs\";\nimport { dirname, isAbsolute, join, relative, resolve as resolvePath, sep } from \"node:path\";\n\n/**\n * Resolve a path to its canonical (real) form, following symlinks.\n * Falls back to the raw path if resolution fails (e.g. the target does\n * not exist yet), so that callers never crash on missing filesystem\n * entries.\n */\nexport function canonicalizePath(path: string): string {\n\ttry {\n\t\treturn realpathSync(path);\n\t} catch {\n\t\treturn path;\n\t}\n}\n\n/**\n * Returns true if the value is NOT a package source (npm:, git:, etc.)\n * or a URL protocol. Bare names and relative paths without ./ prefix\n * are considered local.\n */\nexport function isLocalPath(value: string): boolean {\n\tconst trimmed = value.trim();\n\t// Known non-local prefixes\n\tif (\n\t\ttrimmed.startsWith(\"npm:\") ||\n\t\ttrimmed.startsWith(\"git:\") ||\n\t\ttrimmed.startsWith(\"github:\") ||\n\t\ttrimmed.startsWith(\"http:\") ||\n\t\ttrimmed.startsWith(\"https:\") ||\n\t\ttrimmed.startsWith(\"ssh:\")\n\t) {\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nfunction resolveAgainstCwd(filePath: string, cwd: string): string {\n\treturn isAbsolute(filePath) ? resolvePath(filePath) : resolvePath(cwd, filePath);\n}\n\nexport function getCwdRelativePath(filePath: string, cwd: string): string | undefined {\n\tconst resolvedCwd = resolvePath(cwd);\n\tconst resolvedPath = resolveAgainstCwd(filePath, resolvedCwd);\n\tconst relativePath = relative(resolvedCwd, resolvedPath);\n\tconst isInsideCwd =\n\t\trelativePath === \"\" ||\n\t\t(relativePath !== \"..\" && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath));\n\n\treturn isInsideCwd ? relativePath || \".\" : undefined;\n}\n\nexport function formatPathRelativeToCwdOrAbsolute(filePath: string, cwd: string): string {\n\tconst absolutePath = resolveAgainstCwd(filePath, cwd);\n\treturn (getCwdRelativePath(absolutePath, cwd) ?? absolutePath).split(sep).join(\"/\");\n}\n\n/** Find the nearest ancestor directory containing a `.git` entry, or null. */\nfunction findGitRepoRoot(startDir: string): string | null {\n\tlet dir = resolvePath(startDir);\n\twhile (true) {\n\t\tif (existsSync(join(dir, \".git\"))) return dir;\n\t\tconst parent = dirname(dir);\n\t\tif (parent === dir) return null;\n\t\tdir = parent;\n\t}\n}\n\n/**\n * Collect `.agents/<subdir>/` directories walking from startDir up to the git\n * root, cwd-first (so a closer dir overrides an ancestor under first-match-wins).\n * When startDir is not inside a git repo, the walk continues to the filesystem\n * root, mirroring the agent/skill ancestor scanners.\n */\nexport function collectAgentsAncestorDirs(startDir: string, subdir: string): string[] {\n\tconst dirs: string[] = [];\n\tconst resolvedStart = resolvePath(startDir);\n\tconst gitRoot = findGitRepoRoot(resolvedStart);\n\tlet dir = resolvedStart;\n\twhile (true) {\n\t\tdirs.push(join(dir, \".agents\", subdir));\n\t\tif (gitRoot && dir === gitRoot) break;\n\t\tconst parent = dirname(dir);\n\t\tif (parent === dir) break;\n\t\tdir = parent;\n\t}\n\treturn dirs;\n}\n"]}
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMrD;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAMlE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAclD;AAMD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CASpF;AAED,wBAAgB,iCAAiC,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGvF;AAaD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAapF","sourcesContent":["import { existsSync, realpathSync } from \"node:fs\";\nimport { dirname, isAbsolute, join, relative, resolve as resolvePath, sep } from \"node:path\";\n\n/**\n * Resolve a path to its canonical (real) form, following symlinks.\n * Falls back to the raw path if resolution fails (e.g. the target does\n * not exist yet), so that callers never crash on missing filesystem\n * entries.\n */\nexport function canonicalizePath(path: string): string {\n\ttry {\n\t\treturn realpathSync(path);\n\t} catch {\n\t\treturn path;\n\t}\n}\n\n/**\n * Whether `target` is `root` itself or sits inside it.\n *\n * Segment-aware, so `/a/bc` is not treated as being inside `/a/b`. Four private\n * copies of this predicate already exist (`core/extensions/loader.ts`,\n * `core/extensions/plugins/install.ts`, `core/skills.ts`,\n * `core/prompt-templates.ts`); new callers should use this one.\n */\nexport function isPathInside(target: string, root: string): boolean {\n\tconst normalizedRoot = resolvePath(root);\n\tconst normalizedTarget = resolvePath(target);\n\tif (normalizedTarget === normalizedRoot) return true;\n\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\treturn normalizedTarget.startsWith(prefix);\n}\n\n/**\n * Returns true if the value is NOT a package source (npm:, git:, etc.)\n * or a URL protocol. Bare names and relative paths without ./ prefix\n * are considered local.\n */\nexport function isLocalPath(value: string): boolean {\n\tconst trimmed = value.trim();\n\t// Known non-local prefixes\n\tif (\n\t\ttrimmed.startsWith(\"npm:\") ||\n\t\ttrimmed.startsWith(\"git:\") ||\n\t\ttrimmed.startsWith(\"github:\") ||\n\t\ttrimmed.startsWith(\"http:\") ||\n\t\ttrimmed.startsWith(\"https:\") ||\n\t\ttrimmed.startsWith(\"ssh:\")\n\t) {\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nfunction resolveAgainstCwd(filePath: string, cwd: string): string {\n\treturn isAbsolute(filePath) ? resolvePath(filePath) : resolvePath(cwd, filePath);\n}\n\nexport function getCwdRelativePath(filePath: string, cwd: string): string | undefined {\n\tconst resolvedCwd = resolvePath(cwd);\n\tconst resolvedPath = resolveAgainstCwd(filePath, resolvedCwd);\n\tconst relativePath = relative(resolvedCwd, resolvedPath);\n\tconst isInsideCwd =\n\t\trelativePath === \"\" ||\n\t\t(relativePath !== \"..\" && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath));\n\n\treturn isInsideCwd ? relativePath || \".\" : undefined;\n}\n\nexport function formatPathRelativeToCwdOrAbsolute(filePath: string, cwd: string): string {\n\tconst absolutePath = resolveAgainstCwd(filePath, cwd);\n\treturn (getCwdRelativePath(absolutePath, cwd) ?? absolutePath).split(sep).join(\"/\");\n}\n\n/** Find the nearest ancestor directory containing a `.git` entry, or null. */\nfunction findGitRepoRoot(startDir: string): string | null {\n\tlet dir = resolvePath(startDir);\n\twhile (true) {\n\t\tif (existsSync(join(dir, \".git\"))) return dir;\n\t\tconst parent = dirname(dir);\n\t\tif (parent === dir) return null;\n\t\tdir = parent;\n\t}\n}\n\n/**\n * Collect `.agents/<subdir>/` directories walking from startDir up to the git\n * root, cwd-first (so a closer dir overrides an ancestor under first-match-wins).\n * When startDir is not inside a git repo, the walk continues to the filesystem\n * root, mirroring the agent/skill ancestor scanners.\n */\nexport function collectAgentsAncestorDirs(startDir: string, subdir: string): string[] {\n\tconst dirs: string[] = [];\n\tconst resolvedStart = resolvePath(startDir);\n\tconst gitRoot = findGitRepoRoot(resolvedStart);\n\tlet dir = resolvedStart;\n\twhile (true) {\n\t\tdirs.push(join(dir, \".agents\", subdir));\n\t\tif (gitRoot && dir === gitRoot) break;\n\t\tconst parent = dirname(dir);\n\t\tif (parent === dir) break;\n\t\tdir = parent;\n\t}\n\treturn dirs;\n}\n"]}
@@ -14,6 +14,22 @@ export function canonicalizePath(path) {
14
14
  return path;
15
15
  }
16
16
  }
17
+ /**
18
+ * Whether `target` is `root` itself or sits inside it.
19
+ *
20
+ * Segment-aware, so `/a/bc` is not treated as being inside `/a/b`. Four private
21
+ * copies of this predicate already exist (`core/extensions/loader.ts`,
22
+ * `core/extensions/plugins/install.ts`, `core/skills.ts`,
23
+ * `core/prompt-templates.ts`); new callers should use this one.
24
+ */
25
+ export function isPathInside(target, root) {
26
+ const normalizedRoot = resolvePath(root);
27
+ const normalizedTarget = resolvePath(target);
28
+ if (normalizedTarget === normalizedRoot)
29
+ return true;
30
+ const prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;
31
+ return normalizedTarget.startsWith(prefix);
32
+ }
17
33
  /**
18
34
  * Returns true if the value is NOT a package source (npm:, git:, etc.)
19
35
  * or a URL protocol. Bare names and relative paths without ./ prefix
@@ -1 +1 @@
1
- {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, realpathSync } from \"node:fs\";\nimport { dirname, isAbsolute, join, relative, resolve as resolvePath, sep } from \"node:path\";\n\n/**\n * Resolve a path to its canonical (real) form, following symlinks.\n * Falls back to the raw path if resolution fails (e.g. the target does\n * not exist yet), so that callers never crash on missing filesystem\n * entries.\n */\nexport function canonicalizePath(path: string): string {\n\ttry {\n\t\treturn realpathSync(path);\n\t} catch {\n\t\treturn path;\n\t}\n}\n\n/**\n * Returns true if the value is NOT a package source (npm:, git:, etc.)\n * or a URL protocol. Bare names and relative paths without ./ prefix\n * are considered local.\n */\nexport function isLocalPath(value: string): boolean {\n\tconst trimmed = value.trim();\n\t// Known non-local prefixes\n\tif (\n\t\ttrimmed.startsWith(\"npm:\") ||\n\t\ttrimmed.startsWith(\"git:\") ||\n\t\ttrimmed.startsWith(\"github:\") ||\n\t\ttrimmed.startsWith(\"http:\") ||\n\t\ttrimmed.startsWith(\"https:\") ||\n\t\ttrimmed.startsWith(\"ssh:\")\n\t) {\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nfunction resolveAgainstCwd(filePath: string, cwd: string): string {\n\treturn isAbsolute(filePath) ? resolvePath(filePath) : resolvePath(cwd, filePath);\n}\n\nexport function getCwdRelativePath(filePath: string, cwd: string): string | undefined {\n\tconst resolvedCwd = resolvePath(cwd);\n\tconst resolvedPath = resolveAgainstCwd(filePath, resolvedCwd);\n\tconst relativePath = relative(resolvedCwd, resolvedPath);\n\tconst isInsideCwd =\n\t\trelativePath === \"\" ||\n\t\t(relativePath !== \"..\" && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath));\n\n\treturn isInsideCwd ? relativePath || \".\" : undefined;\n}\n\nexport function formatPathRelativeToCwdOrAbsolute(filePath: string, cwd: string): string {\n\tconst absolutePath = resolveAgainstCwd(filePath, cwd);\n\treturn (getCwdRelativePath(absolutePath, cwd) ?? absolutePath).split(sep).join(\"/\");\n}\n\n/** Find the nearest ancestor directory containing a `.git` entry, or null. */\nfunction findGitRepoRoot(startDir: string): string | null {\n\tlet dir = resolvePath(startDir);\n\twhile (true) {\n\t\tif (existsSync(join(dir, \".git\"))) return dir;\n\t\tconst parent = dirname(dir);\n\t\tif (parent === dir) return null;\n\t\tdir = parent;\n\t}\n}\n\n/**\n * Collect `.agents/<subdir>/` directories walking from startDir up to the git\n * root, cwd-first (so a closer dir overrides an ancestor under first-match-wins).\n * When startDir is not inside a git repo, the walk continues to the filesystem\n * root, mirroring the agent/skill ancestor scanners.\n */\nexport function collectAgentsAncestorDirs(startDir: string, subdir: string): string[] {\n\tconst dirs: string[] = [];\n\tconst resolvedStart = resolvePath(startDir);\n\tconst gitRoot = findGitRepoRoot(resolvedStart);\n\tlet dir = resolvedStart;\n\twhile (true) {\n\t\tdirs.push(join(dir, \".agents\", subdir));\n\t\tif (gitRoot && dir === gitRoot) break;\n\t\tconst parent = dirname(dir);\n\t\tif (parent === dir) break;\n\t\tdir = parent;\n\t}\n\treturn dirs;\n}\n"]}
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, realpathSync } from \"node:fs\";\nimport { dirname, isAbsolute, join, relative, resolve as resolvePath, sep } from \"node:path\";\n\n/**\n * Resolve a path to its canonical (real) form, following symlinks.\n * Falls back to the raw path if resolution fails (e.g. the target does\n * not exist yet), so that callers never crash on missing filesystem\n * entries.\n */\nexport function canonicalizePath(path: string): string {\n\ttry {\n\t\treturn realpathSync(path);\n\t} catch {\n\t\treturn path;\n\t}\n}\n\n/**\n * Whether `target` is `root` itself or sits inside it.\n *\n * Segment-aware, so `/a/bc` is not treated as being inside `/a/b`. Four private\n * copies of this predicate already exist (`core/extensions/loader.ts`,\n * `core/extensions/plugins/install.ts`, `core/skills.ts`,\n * `core/prompt-templates.ts`); new callers should use this one.\n */\nexport function isPathInside(target: string, root: string): boolean {\n\tconst normalizedRoot = resolvePath(root);\n\tconst normalizedTarget = resolvePath(target);\n\tif (normalizedTarget === normalizedRoot) return true;\n\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\treturn normalizedTarget.startsWith(prefix);\n}\n\n/**\n * Returns true if the value is NOT a package source (npm:, git:, etc.)\n * or a URL protocol. Bare names and relative paths without ./ prefix\n * are considered local.\n */\nexport function isLocalPath(value: string): boolean {\n\tconst trimmed = value.trim();\n\t// Known non-local prefixes\n\tif (\n\t\ttrimmed.startsWith(\"npm:\") ||\n\t\ttrimmed.startsWith(\"git:\") ||\n\t\ttrimmed.startsWith(\"github:\") ||\n\t\ttrimmed.startsWith(\"http:\") ||\n\t\ttrimmed.startsWith(\"https:\") ||\n\t\ttrimmed.startsWith(\"ssh:\")\n\t) {\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nfunction resolveAgainstCwd(filePath: string, cwd: string): string {\n\treturn isAbsolute(filePath) ? resolvePath(filePath) : resolvePath(cwd, filePath);\n}\n\nexport function getCwdRelativePath(filePath: string, cwd: string): string | undefined {\n\tconst resolvedCwd = resolvePath(cwd);\n\tconst resolvedPath = resolveAgainstCwd(filePath, resolvedCwd);\n\tconst relativePath = relative(resolvedCwd, resolvedPath);\n\tconst isInsideCwd =\n\t\trelativePath === \"\" ||\n\t\t(relativePath !== \"..\" && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath));\n\n\treturn isInsideCwd ? relativePath || \".\" : undefined;\n}\n\nexport function formatPathRelativeToCwdOrAbsolute(filePath: string, cwd: string): string {\n\tconst absolutePath = resolveAgainstCwd(filePath, cwd);\n\treturn (getCwdRelativePath(absolutePath, cwd) ?? absolutePath).split(sep).join(\"/\");\n}\n\n/** Find the nearest ancestor directory containing a `.git` entry, or null. */\nfunction findGitRepoRoot(startDir: string): string | null {\n\tlet dir = resolvePath(startDir);\n\twhile (true) {\n\t\tif (existsSync(join(dir, \".git\"))) return dir;\n\t\tconst parent = dirname(dir);\n\t\tif (parent === dir) return null;\n\t\tdir = parent;\n\t}\n}\n\n/**\n * Collect `.agents/<subdir>/` directories walking from startDir up to the git\n * root, cwd-first (so a closer dir overrides an ancestor under first-match-wins).\n * When startDir is not inside a git repo, the walk continues to the filesystem\n * root, mirroring the agent/skill ancestor scanners.\n */\nexport function collectAgentsAncestorDirs(startDir: string, subdir: string): string[] {\n\tconst dirs: string[] = [];\n\tconst resolvedStart = resolvePath(startDir);\n\tconst gitRoot = findGitRepoRoot(resolvedStart);\n\tlet dir = resolvedStart;\n\twhile (true) {\n\t\tdirs.push(join(dir, \".agents\", subdir));\n\t\tif (gitRoot && dir === gitRoot) break;\n\t\tconst parent = dirname(dir);\n\t\tif (parent === dir) break;\n\t\tdir = parent;\n\t}\n\treturn dirs;\n}\n"]}
package/docs/routing.md CHANGED
@@ -80,6 +80,6 @@ multiple `Task` calls; there is no batch-dispatch API.
80
80
  - **Bounded nesting (default: none).** Nesting is capped by `maxSubagentDepth` (default `1`). The root seeds the cap into `HOOCODE_SUBAGENT_MAX_DEPTH`; each spawned child is stamped with its depth (`HOOCODE_SUBAGENT_DEPTH` = parent + 1). The `Task`/`TaskOutput` tools are registered only while a process's depth is below the cap, and the `DispatchEvaluator` enforces the same bound as defense in depth — so at the default cap subagents cannot recursively dispatch. When the cap is raised, nested pools (depth ≥ 1) run with a reduced concurrency so the worst-case live process count stays a fixed function of depth (e.g. `5 + 5×2 = 15` at depth 2), with no shared state to leak on crash.
81
81
  - **Delegation is opt-in per agent.** A subagent only receives the `Task` tool when its agent definition sets `delegate` *and* it is spawned below the cap (`childDepth < maxSubagentDepth`). On spawn, such an agent has `Task`/`TaskOutput` added to its tool allowlist and `--enable-subagents` propagated, so it can dispatch one further level. Every other agent keeps its declared sandbox and cannot delegate, preserving the deliberate "Task is not a normal tool" boundary. `delegate: true` permits any subagent type; `delegate: explore, plan` scopes it to those types (forwarded as `--delegate-allow`; the Task tool rejects out-of-scope dispatches). See `examples/agents/orchestrator.md` for a ready-to-use delegating agent.
82
82
 
83
- To exercise depth-2 end to end: drop the orchestrator into a discovered agents dir (e.g. `.hoocode/agents/`), then run with `--enable-subagents --max-subagent-depth 2` and delegate to it. A `[DISPATCH] … depth=2 …` line confirms a subagent delegated.
83
+ To exercise depth-2 end to end: drop the orchestrator into a discovered agents dir (e.g. `.hoocode/agents/`), then run with `--enable-subagents --max-subagent-depth 2` and delegate to it. A `[DISPATCH] … depth=2 …` line confirms a subagent delegated. That line is written to stderr only when no TUI owns the terminal (`--print`, RPC, CI) — in an interactive session it would corrupt the differential render, so check `.hoocode/dispatch/<task_id>/dispatch-log.json` (which carries the same `depth`) or set `HOOCODE_DEBUG_AGENT_LOG=1` to tee it to `hoocode-debug.log`.
84
84
  - The pool prioritizes `explore` and `review` tasks over `doc` tasks because they often block downstream work.
85
85
  - On completion, the subagent writes `.hoocode/dispatch/<task_id>/result.json` (verified by the parent) and the pool writes `.hoocode/dispatch/<task_id>/output.json` (raw process outcome).
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-extension-custom-provider-anthropic",
3
3
  "private": true,
4
- "version": "0.3.21",
4
+ "version": "0.3.23",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "bun": ">=1.0.0"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-extension-custom-provider-gitlab-duo",
3
3
  "private": true,
4
- "version": "0.3.21",
4
+ "version": "0.3.23",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "bun": ">=1.0.0"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-extension-sandbox",
3
3
  "private": true,
4
- "version": "0.3.21",
4
+ "version": "0.3.23",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "bun": ">=1.0.0"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-extension-with-deps",
3
3
  "private": true,
4
- "version": "0.3.21",
4
+ "version": "0.3.23",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "bun": ">=1.0.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-agent",
3
- "version": "0.5.21",
3
+ "version": "0.5.23",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "hoocodeConfig": {
@@ -49,9 +49,9 @@
49
49
  "prepublishOnly": "npm run clean && npm run build"
50
50
  },
51
51
  "dependencies": {
52
- "@kolisachint/hoocode-agent-core": "^0.5.21",
53
- "@kolisachint/hoocode-ai": "^0.5.21",
54
- "@kolisachint/hoocode-tui": "^0.5.21",
52
+ "@kolisachint/hoocode-agent-core": "^0.5.23",
53
+ "@kolisachint/hoocode-ai": "^0.5.23",
54
+ "@kolisachint/hoocode-tui": "^0.5.23",
55
55
  "@silvia-odwyer/photon-node": "^0.3.4",
56
56
  "chalk": "^5.5.0",
57
57
  "cli-highlight": "^2.1.11",
@@ -80,6 +80,7 @@
80
80
  "@mariozechner/clipboard": "^0.3.5"
81
81
  },
82
82
  "devDependencies": {
83
+ "@github/copilot-sdk": "1.0.11",
83
84
  "@types/hosted-git-info": "^3.0.5",
84
85
  "@types/node": "^24.3.0",
85
86
  "@types/proper-lockfile": "^4.1.4",