@caupulican/pi-adaptative 0.81.35 → 0.81.37

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 (148) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/cli/args.d.ts.map +1 -1
  3. package/dist/cli/args.js +10 -8
  4. package/dist/cli/args.js.map +1 -1
  5. package/dist/core/agent-session.d.ts +5 -0
  6. package/dist/core/agent-session.d.ts.map +1 -1
  7. package/dist/core/agent-session.js +17 -0
  8. package/dist/core/agent-session.js.map +1 -1
  9. package/dist/core/autonomy/approval-gate.d.ts.map +1 -1
  10. package/dist/core/autonomy/approval-gate.js +2 -0
  11. package/dist/core/autonomy/approval-gate.js.map +1 -1
  12. package/dist/core/autonomy/foreground-envelope.d.ts.map +1 -1
  13. package/dist/core/autonomy/foreground-envelope.js +2 -0
  14. package/dist/core/autonomy/foreground-envelope.js.map +1 -1
  15. package/dist/core/autonomy/gates.d.ts.map +1 -1
  16. package/dist/core/autonomy/gates.js +9 -2
  17. package/dist/core/autonomy/gates.js.map +1 -1
  18. package/dist/core/autonomy/risk-assessment.d.ts.map +1 -1
  19. package/dist/core/autonomy/risk-assessment.js +2 -0
  20. package/dist/core/autonomy/risk-assessment.js.map +1 -1
  21. package/dist/core/bash-execution-controller.d.ts +9 -4
  22. package/dist/core/bash-execution-controller.d.ts.map +1 -1
  23. package/dist/core/bash-execution-controller.js +7 -6
  24. package/dist/core/bash-execution-controller.js.map +1 -1
  25. package/dist/core/bash-executor.d.ts +2 -0
  26. package/dist/core/bash-executor.d.ts.map +1 -1
  27. package/dist/core/bash-executor.js +3 -2
  28. package/dist/core/bash-executor.js.map +1 -1
  29. package/dist/core/default-tool-surface.d.ts +7 -0
  30. package/dist/core/default-tool-surface.d.ts.map +1 -1
  31. package/dist/core/default-tool-surface.js +29 -10
  32. package/dist/core/default-tool-surface.js.map +1 -1
  33. package/dist/core/doctor.d.ts +10 -10
  34. package/dist/core/doctor.d.ts.map +1 -1
  35. package/dist/core/doctor.js +30 -20
  36. package/dist/core/doctor.js.map +1 -1
  37. package/dist/core/exec.d.ts +2 -0
  38. package/dist/core/exec.d.ts.map +1 -1
  39. package/dist/core/exec.js +1 -0
  40. package/dist/core/exec.js.map +1 -1
  41. package/dist/core/extensions/types.d.ts +13 -3
  42. package/dist/core/extensions/types.d.ts.map +1 -1
  43. package/dist/core/extensions/types.js +3 -0
  44. package/dist/core/extensions/types.js.map +1 -1
  45. package/dist/core/memory/memory-manager.d.ts.map +1 -1
  46. package/dist/core/memory/memory-manager.js +1 -0
  47. package/dist/core/memory/memory-manager.js.map +1 -1
  48. package/dist/core/model-capability.d.ts.map +1 -1
  49. package/dist/core/model-capability.js +2 -0
  50. package/dist/core/model-capability.js.map +1 -1
  51. package/dist/core/model-router/tool-escalation.d.ts.map +1 -1
  52. package/dist/core/model-router/tool-escalation.js +17 -3
  53. package/dist/core/model-router/tool-escalation.js.map +1 -1
  54. package/dist/core/python-runtime.d.ts +46 -0
  55. package/dist/core/python-runtime.d.ts.map +1 -0
  56. package/dist/core/python-runtime.js +157 -0
  57. package/dist/core/python-runtime.js.map +1 -0
  58. package/dist/core/runtime-builder.d.ts +4 -0
  59. package/dist/core/runtime-builder.d.ts.map +1 -1
  60. package/dist/core/runtime-builder.js +31 -9
  61. package/dist/core/runtime-builder.js.map +1 -1
  62. package/dist/core/security/untrusted-boundary.d.ts +2 -2
  63. package/dist/core/security/untrusted-boundary.d.ts.map +1 -1
  64. package/dist/core/security/untrusted-boundary.js +4 -3
  65. package/dist/core/security/untrusted-boundary.js.map +1 -1
  66. package/dist/core/settings-manager.d.ts.map +1 -1
  67. package/dist/core/settings-manager.js +2 -0
  68. package/dist/core/settings-manager.js.map +1 -1
  69. package/dist/core/slash-commands.d.ts.map +1 -1
  70. package/dist/core/slash-commands.js +8 -0
  71. package/dist/core/slash-commands.js.map +1 -1
  72. package/dist/core/system-prompt.d.ts.map +1 -1
  73. package/dist/core/system-prompt.js +13 -2
  74. package/dist/core/system-prompt.js.map +1 -1
  75. package/dist/core/tasks/session-task-state.d.ts +10 -0
  76. package/dist/core/tasks/session-task-state.d.ts.map +1 -0
  77. package/dist/core/tasks/session-task-state.js +29 -0
  78. package/dist/core/tasks/session-task-state.js.map +1 -0
  79. package/dist/core/tasks/task-command.d.ts +30 -0
  80. package/dist/core/tasks/task-command.d.ts.map +1 -0
  81. package/dist/core/tasks/task-command.js +79 -0
  82. package/dist/core/tasks/task-command.js.map +1 -0
  83. package/dist/core/tasks/task-state.d.ts +71 -0
  84. package/dist/core/tasks/task-state.d.ts.map +1 -0
  85. package/dist/core/tasks/task-state.js +372 -0
  86. package/dist/core/tasks/task-state.js.map +1 -0
  87. package/dist/core/tool-selection/tool-selection-controller.d.ts.map +1 -1
  88. package/dist/core/tool-selection/tool-selection-controller.js +1 -1
  89. package/dist/core/tool-selection/tool-selection-controller.js.map +1 -1
  90. package/dist/core/tools/bash.d.ts +20 -2
  91. package/dist/core/tools/bash.d.ts.map +1 -1
  92. package/dist/core/tools/bash.js +122 -197
  93. package/dist/core/tools/bash.js.map +1 -1
  94. package/dist/core/tools/find.d.ts.map +1 -1
  95. package/dist/core/tools/find.js +3 -0
  96. package/dist/core/tools/find.js.map +1 -1
  97. package/dist/core/tools/grep.d.ts.map +1 -1
  98. package/dist/core/tools/grep.js +3 -0
  99. package/dist/core/tools/grep.js.map +1 -1
  100. package/dist/core/tools/index.d.ts +9 -6
  101. package/dist/core/tools/index.d.ts.map +1 -1
  102. package/dist/core/tools/index.js +24 -9
  103. package/dist/core/tools/index.js.map +1 -1
  104. package/dist/core/tools/python.d.ts +64 -0
  105. package/dist/core/tools/python.d.ts.map +1 -0
  106. package/dist/core/tools/python.js +241 -0
  107. package/dist/core/tools/python.js.map +1 -0
  108. package/dist/core/tools/shell-contract-router.d.ts +13 -0
  109. package/dist/core/tools/shell-contract-router.d.ts.map +1 -0
  110. package/dist/core/tools/shell-contract-router.js +295 -0
  111. package/dist/core/tools/shell-contract-router.js.map +1 -0
  112. package/dist/core/tools/task-steps.d.ts +46 -0
  113. package/dist/core/tools/task-steps.d.ts.map +1 -0
  114. package/dist/core/tools/task-steps.js +160 -0
  115. package/dist/core/tools/task-steps.js.map +1 -0
  116. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  117. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  118. package/dist/modes/interactive/interactive-mode.js +12 -0
  119. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  120. package/dist/modes/interactive/session-flow-commands.d.ts +10 -0
  121. package/dist/modes/interactive/session-flow-commands.d.ts.map +1 -1
  122. package/dist/modes/interactive/session-flow-commands.js +43 -0
  123. package/dist/modes/interactive/session-flow-commands.js.map +1 -1
  124. package/dist/utils/shell.d.ts +6 -8
  125. package/dist/utils/shell.d.ts.map +1 -1
  126. package/dist/utils/shell.js +61 -70
  127. package/dist/utils/shell.js.map +1 -1
  128. package/dist/utils/tools-manager.d.ts +23 -3
  129. package/dist/utils/tools-manager.d.ts.map +1 -1
  130. package/dist/utils/tools-manager.js +94 -11
  131. package/dist/utils/tools-manager.js.map +1 -1
  132. package/docs/index.md +2 -0
  133. package/docs/python.md +43 -0
  134. package/docs/settings.md +7 -3
  135. package/docs/task-steps.md +90 -0
  136. package/docs/termux.md +7 -0
  137. package/docs/usage.md +5 -6
  138. package/docs/windows.md +15 -12
  139. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  140. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  141. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  142. package/examples/extensions/sandbox/package-lock.json +2 -2
  143. package/examples/extensions/sandbox/package.json +1 -1
  144. package/examples/extensions/with-deps/package-lock.json +2 -2
  145. package/examples/extensions/with-deps/package.json +1 -1
  146. package/npm-shrinkwrap.json +32 -37
  147. package/package.json +9 -7
  148. package/scripts/postinstall-python-runtime.mjs +20 -0
@@ -1 +1 @@
1
- {"version":3,"file":"tool-escalation.d.ts","sourceRoot":"","sources":["../../../src/core/model-router/tool-escalation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AA6F1D,wBAAgB,6BAA6B,CAAC,OAAO,EAAE;IACtD,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAeV","sourcesContent":["import type { ModelTier } from \"../autonomy/contracts.ts\";\n\nconst READ_ONLY_TOOL_NAMES = new Set([\n\t\"read\",\n\t\"grep\",\n\t\"find\",\n\t\"ls\",\n\t\"list\",\n\t\"search\",\n\t\"glob\",\n\t\"view_file\",\n\t\"list_dir\",\n\t\"grep_search\",\n\t\"search_web\",\n\t\"read_url_content\",\n\t\"read_browser_page\",\n]);\n\nconst SHELL_TOOL_NAMES = new Set([\"bash\", \"exec\", \"execute\", \"run\", \"run_command\", \"shell\"]);\n\nconst READ_ONLY_COMMANDS = new Set([\n\t\"awk\",\n\t\"cat\",\n\t\"date\",\n\t\"df\",\n\t\"du\",\n\t\"env\",\n\t\"git\",\n\t\"grep\",\n\t\"head\",\n\t\"jq\",\n\t\"ls\",\n\t\"node\",\n\t\"npm\",\n\t\"pnpm\",\n\t\"pwd\",\n\t\"rg\",\n\t\"sed\",\n\t\"tail\",\n\t\"test\",\n\t\"tsc\",\n\t\"wc\",\n\t\"which\",\n\t\"yarn\",\n]);\n\nconst READ_ONLY_GIT_SUBCOMMANDS = new Set([\"branch\", \"diff\", \"log\", \"rev-parse\", \"show\", \"status\", \"tag\"]);\nconst READ_ONLY_NPM_SUBCOMMANDS = new Set([\"info\", \"list\", \"ls\", \"outdated\", \"view\", \"whoami\"]);\nconst MUTATING_SHELL_TOKEN_RE =\n\t/(^|\\s)(>|>>|2>|&>|tee\\b|rm\\b|mv\\b|cp\\b|mkdir\\b|touch\\b|chmod\\b|chown\\b|install\\b|commit\\b|push\\b|publish\\b|deploy\\b|apply\\b|add\\b|checkout\\b|switch\\b|reset\\b|clean\\b|stash\\b|merge\\b|rebase\\b|npm\\s+(?:i|install|ci|update|publish|run)\\b|pnpm\\s+(?:i|install|update|publish|run)\\b|yarn\\s+(?:add|install|upgrade|publish|run)\\b)/i;\nconst UNSAFE_NESTED_SHELL_EXECUTION_RE = /(`|\\$\\(|\\bfind\\b[\\s\\S]*\\s-exec(?:dir)?\\b|\\bxargs\\b)/i;\nconst MUTATING_TOOL_NAME_RE =\n\t/(bash|exec|execute|run|shell|write|edit|patch|replace|delete|remove|move|rename|create|mkdir|touch|install|commit|push|publish|deploy|apply)/i;\n\nfunction getShellCommand(args: unknown): string | undefined {\n\tif (!args || typeof args !== \"object\") return undefined;\n\tconst record = args as Record<string, unknown>;\n\tconst command = record.command ?? record.cmd ?? record.shellCommand;\n\treturn typeof command === \"string\" ? command.trim() : undefined;\n}\n\nfunction commandName(segment: string): string | undefined {\n\tconst first = segment.trim().match(/^[A-Za-z0-9_./-]+/)?.[0];\n\tif (!first) return undefined;\n\tconst parts = first.split(\"/\");\n\treturn parts[parts.length - 1]?.toLowerCase();\n}\n\nfunction commandArg(segment: string, index: number): string | undefined {\n\treturn segment.trim().split(/\\s+/)[index]?.toLowerCase();\n}\n\nfunction isReadOnlyShellSegment(segment: string): boolean {\n\tconst name = commandName(segment);\n\tif (!name || !READ_ONLY_COMMANDS.has(name)) return false;\n\tif (name === \"git\") {\n\t\tconst subcommand = commandArg(segment, 1);\n\t\treturn Boolean(subcommand && READ_ONLY_GIT_SUBCOMMANDS.has(subcommand));\n\t}\n\tif (name === \"npm\" || name === \"pnpm\" || name === \"yarn\") {\n\t\tconst subcommand = commandArg(segment, 1);\n\t\treturn Boolean(subcommand && READ_ONLY_NPM_SUBCOMMANDS.has(subcommand));\n\t}\n\treturn true;\n}\n\nfunction isReadOnlyShellCommand(command: string): boolean {\n\tif (!command || MUTATING_SHELL_TOKEN_RE.test(command) || UNSAFE_NESTED_SHELL_EXECUTION_RE.test(command))\n\t\treturn false;\n\tconst segments = command.split(/\\s*(?:&&|\\|\\||[;|\\r\\n])\\s*/).map((segment) => segment.trim());\n\treturn segments.length > 0 && segments.every((segment) => segment.length > 0 && isReadOnlyShellSegment(segment));\n}\n\nexport function shouldEscalateModelRouterTool(options: {\n\ttier: ModelTier;\n\ttoolName: string;\n\targs?: unknown;\n\t/** The route's reasonCode; executor-lane turns carry \"executor_direct\". */\n\treasonCode?: string;\n}): boolean {\n\tif (options.tier !== \"cheap\") return false;\n\tconst toolName = options.toolName.trim().toLowerCase();\n\t// Executor-lane turns (G16) exist to run exactly one tool: run_toolkit_script, which enforces\n\t// its own safety (danger confirmation, structural exit-code contract). Escalating on it would\n\t// abort every executor turn at the moment it does its job. Any OTHER mutating tool still\n\t// escalates to the expensive model as usual.\n\tif (options.reasonCode === \"executor_direct\" && toolName === \"run_toolkit_script\") return false;\n\tif (!toolName) return true;\n\tif (READ_ONLY_TOOL_NAMES.has(toolName)) return false;\n\tif (SHELL_TOOL_NAMES.has(toolName)) {\n\t\tconst command = getShellCommand(options.args);\n\t\treturn command ? !isReadOnlyShellCommand(command) : true;\n\t}\n\treturn MUTATING_TOOL_NAME_RE.test(toolName) || !toolName.startsWith(\"read_\");\n}\n"]}
1
+ {"version":3,"file":"tool-escalation.d.ts","sourceRoot":"","sources":["../../../src/core/model-router/tool-escalation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AA2G1D,wBAAgB,6BAA6B,CAAC,OAAO,EAAE;IACtD,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAeV","sourcesContent":["import type { ModelTier } from \"../autonomy/contracts.ts\";\n\nconst READ_ONLY_TOOL_NAMES = new Set([\n\t\"read\",\n\t\"grep\",\n\t\"find\",\n\t\"ls\",\n\t\"list\",\n\t\"search\",\n\t\"glob\",\n\t\"view_file\",\n\t\"list_dir\",\n\t\"grep_search\",\n\t\"search_web\",\n\t\"read_url_content\",\n\t\"read_browser_page\",\n]);\n\nconst SHELL_TOOL_NAMES = new Set([\"bash\", \"powershell\", \"exec\", \"execute\", \"run\", \"run_command\", \"shell\"]);\n\nconst READ_ONLY_COMMANDS = new Set([\n\t\"awk\",\n\t\"cat\",\n\t\"date\",\n\t\"df\",\n\t\"du\",\n\t\"env\",\n\t\"format-list\",\n\t\"format-table\",\n\t\"get-childitem\",\n\t\"get-command\",\n\t\"get-content\",\n\t\"get-item\",\n\t\"get-location\",\n\t\"get-process\",\n\t\"git\",\n\t\"grep\",\n\t\"head\",\n\t\"jq\",\n\t\"ls\",\n\t\"node\",\n\t\"npm\",\n\t\"pnpm\",\n\t\"pwd\",\n\t\"resolve-path\",\n\t\"rg\",\n\t\"sed\",\n\t\"select-object\",\n\t\"select-string\",\n\t\"tail\",\n\t\"test\",\n\t\"test-path\",\n\t\"tsc\",\n\t\"wc\",\n\t\"where-object\",\n\t\"which\",\n\t\"write-output\",\n\t\"yarn\",\n]);\n\nconst READ_ONLY_GIT_SUBCOMMANDS = new Set([\"branch\", \"diff\", \"log\", \"rev-parse\", \"show\", \"status\", \"tag\"]);\nconst READ_ONLY_NPM_SUBCOMMANDS = new Set([\"info\", \"list\", \"ls\", \"outdated\", \"view\", \"whoami\"]);\nconst MUTATING_SHELL_TOKEN_RE =\n\t/(^|\\s)(>|>>|2>|&>|tee\\b|rm\\b|mv\\b|cp\\b|mkdir\\b|touch\\b|chmod\\b|chown\\b|install\\b|commit\\b|push\\b|publish\\b|deploy\\b|apply\\b|add\\b|checkout\\b|switch\\b|reset\\b|clean\\b|stash\\b|merge\\b|rebase\\b|remove-item\\b|move-item\\b|copy-item\\b|new-item\\b|rename-item\\b|set-content\\b|add-content\\b|out-file\\b|set-item\\b|start-process\\b|npm\\s+(?:i|install|ci|update|publish|run)\\b|pnpm\\s+(?:i|install|update|publish|run)\\b|yarn\\s+(?:add|install|upgrade|publish|run)\\b)/i;\nconst UNSAFE_NESTED_SHELL_EXECUTION_RE = /(`|\\$\\(|\\bfind\\b[\\s\\S]*\\s-exec(?:dir)?\\b|\\bxargs\\b)/i;\nconst MUTATING_TOOL_NAME_RE =\n\t/(bash|powershell|exec|execute|run|shell|write|edit|patch|replace|delete|remove|move|rename|create|mkdir|touch|install|commit|push|publish|deploy|apply)/i;\n\nfunction getShellCommand(args: unknown): string | undefined {\n\tif (!args || typeof args !== \"object\") return undefined;\n\tconst record = args as Record<string, unknown>;\n\tconst command = record.command ?? record.cmd ?? record.shellCommand;\n\treturn typeof command === \"string\" ? command.trim() : undefined;\n}\n\nfunction commandName(segment: string): string | undefined {\n\tconst first = segment.trim().match(/^[A-Za-z0-9_./-]+/)?.[0];\n\tif (!first) return undefined;\n\tconst parts = first.split(\"/\");\n\treturn parts[parts.length - 1]?.toLowerCase();\n}\n\nfunction commandArg(segment: string, index: number): string | undefined {\n\treturn segment.trim().split(/\\s+/)[index]?.toLowerCase();\n}\n\nfunction isReadOnlyShellSegment(segment: string): boolean {\n\tconst name = commandName(segment);\n\tif (!name || !READ_ONLY_COMMANDS.has(name)) return false;\n\tif (name === \"git\") {\n\t\tconst subcommand = commandArg(segment, 1);\n\t\treturn Boolean(subcommand && READ_ONLY_GIT_SUBCOMMANDS.has(subcommand));\n\t}\n\tif (name === \"npm\" || name === \"pnpm\" || name === \"yarn\") {\n\t\tconst subcommand = commandArg(segment, 1);\n\t\treturn Boolean(subcommand && READ_ONLY_NPM_SUBCOMMANDS.has(subcommand));\n\t}\n\treturn true;\n}\n\nfunction isReadOnlyShellCommand(command: string): boolean {\n\tif (!command || MUTATING_SHELL_TOKEN_RE.test(command) || UNSAFE_NESTED_SHELL_EXECUTION_RE.test(command))\n\t\treturn false;\n\tconst segments = command.split(/\\s*(?:&&|\\|\\||[;|\\r\\n])\\s*/).map((segment) => segment.trim());\n\treturn segments.length > 0 && segments.every((segment) => segment.length > 0 && isReadOnlyShellSegment(segment));\n}\n\nexport function shouldEscalateModelRouterTool(options: {\n\ttier: ModelTier;\n\ttoolName: string;\n\targs?: unknown;\n\t/** The route's reasonCode; executor-lane turns carry \"executor_direct\". */\n\treasonCode?: string;\n}): boolean {\n\tif (options.tier !== \"cheap\") return false;\n\tconst toolName = options.toolName.trim().toLowerCase();\n\t// Executor-lane turns (G16) exist to run exactly one tool: run_toolkit_script, which enforces\n\t// its own safety (danger confirmation, structural exit-code contract). Escalating on it would\n\t// abort every executor turn at the moment it does its job. Any OTHER mutating tool still\n\t// escalates to the expensive model as usual.\n\tif (options.reasonCode === \"executor_direct\" && toolName === \"run_toolkit_script\") return false;\n\tif (!toolName) return true;\n\tif (READ_ONLY_TOOL_NAMES.has(toolName)) return false;\n\tif (SHELL_TOOL_NAMES.has(toolName)) {\n\t\tconst command = getShellCommand(options.args);\n\t\treturn command ? !isReadOnlyShellCommand(command) : true;\n\t}\n\treturn MUTATING_TOOL_NAME_RE.test(toolName) || !toolName.startsWith(\"read_\");\n}\n"]}
@@ -13,7 +13,7 @@ const READ_ONLY_TOOL_NAMES = new Set([
13
13
  "read_url_content",
14
14
  "read_browser_page",
15
15
  ]);
16
- const SHELL_TOOL_NAMES = new Set(["bash", "exec", "execute", "run", "run_command", "shell"]);
16
+ const SHELL_TOOL_NAMES = new Set(["bash", "powershell", "exec", "execute", "run", "run_command", "shell"]);
17
17
  const READ_ONLY_COMMANDS = new Set([
18
18
  "awk",
19
19
  "cat",
@@ -21,6 +21,14 @@ const READ_ONLY_COMMANDS = new Set([
21
21
  "df",
22
22
  "du",
23
23
  "env",
24
+ "format-list",
25
+ "format-table",
26
+ "get-childitem",
27
+ "get-command",
28
+ "get-content",
29
+ "get-item",
30
+ "get-location",
31
+ "get-process",
24
32
  "git",
25
33
  "grep",
26
34
  "head",
@@ -30,20 +38,26 @@ const READ_ONLY_COMMANDS = new Set([
30
38
  "npm",
31
39
  "pnpm",
32
40
  "pwd",
41
+ "resolve-path",
33
42
  "rg",
34
43
  "sed",
44
+ "select-object",
45
+ "select-string",
35
46
  "tail",
36
47
  "test",
48
+ "test-path",
37
49
  "tsc",
38
50
  "wc",
51
+ "where-object",
39
52
  "which",
53
+ "write-output",
40
54
  "yarn",
41
55
  ]);
42
56
  const READ_ONLY_GIT_SUBCOMMANDS = new Set(["branch", "diff", "log", "rev-parse", "show", "status", "tag"]);
43
57
  const READ_ONLY_NPM_SUBCOMMANDS = new Set(["info", "list", "ls", "outdated", "view", "whoami"]);
44
- const MUTATING_SHELL_TOKEN_RE = /(^|\s)(>|>>|2>|&>|tee\b|rm\b|mv\b|cp\b|mkdir\b|touch\b|chmod\b|chown\b|install\b|commit\b|push\b|publish\b|deploy\b|apply\b|add\b|checkout\b|switch\b|reset\b|clean\b|stash\b|merge\b|rebase\b|npm\s+(?:i|install|ci|update|publish|run)\b|pnpm\s+(?:i|install|update|publish|run)\b|yarn\s+(?:add|install|upgrade|publish|run)\b)/i;
58
+ const MUTATING_SHELL_TOKEN_RE = /(^|\s)(>|>>|2>|&>|tee\b|rm\b|mv\b|cp\b|mkdir\b|touch\b|chmod\b|chown\b|install\b|commit\b|push\b|publish\b|deploy\b|apply\b|add\b|checkout\b|switch\b|reset\b|clean\b|stash\b|merge\b|rebase\b|remove-item\b|move-item\b|copy-item\b|new-item\b|rename-item\b|set-content\b|add-content\b|out-file\b|set-item\b|start-process\b|npm\s+(?:i|install|ci|update|publish|run)\b|pnpm\s+(?:i|install|update|publish|run)\b|yarn\s+(?:add|install|upgrade|publish|run)\b)/i;
45
59
  const UNSAFE_NESTED_SHELL_EXECUTION_RE = /(`|\$\(|\bfind\b[\s\S]*\s-exec(?:dir)?\b|\bxargs\b)/i;
46
- const MUTATING_TOOL_NAME_RE = /(bash|exec|execute|run|shell|write|edit|patch|replace|delete|remove|move|rename|create|mkdir|touch|install|commit|push|publish|deploy|apply)/i;
60
+ const MUTATING_TOOL_NAME_RE = /(bash|powershell|exec|execute|run|shell|write|edit|patch|replace|delete|remove|move|rename|create|mkdir|touch|install|commit|push|publish|deploy|apply)/i;
47
61
  function getShellCommand(args) {
48
62
  if (!args || typeof args !== "object")
49
63
  return undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"tool-escalation.js","sourceRoot":"","sources":["../../../src/core/model-router/tool-escalation.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ModelTier } from \"../autonomy/contracts.ts\";\n\nconst READ_ONLY_TOOL_NAMES = new Set([\n\t\"read\",\n\t\"grep\",\n\t\"find\",\n\t\"ls\",\n\t\"list\",\n\t\"search\",\n\t\"glob\",\n\t\"view_file\",\n\t\"list_dir\",\n\t\"grep_search\",\n\t\"search_web\",\n\t\"read_url_content\",\n\t\"read_browser_page\",\n]);\n\nconst SHELL_TOOL_NAMES = new Set([\"bash\", \"exec\", \"execute\", \"run\", \"run_command\", \"shell\"]);\n\nconst READ_ONLY_COMMANDS = new Set([\n\t\"awk\",\n\t\"cat\",\n\t\"date\",\n\t\"df\",\n\t\"du\",\n\t\"env\",\n\t\"git\",\n\t\"grep\",\n\t\"head\",\n\t\"jq\",\n\t\"ls\",\n\t\"node\",\n\t\"npm\",\n\t\"pnpm\",\n\t\"pwd\",\n\t\"rg\",\n\t\"sed\",\n\t\"tail\",\n\t\"test\",\n\t\"tsc\",\n\t\"wc\",\n\t\"which\",\n\t\"yarn\",\n]);\n\nconst READ_ONLY_GIT_SUBCOMMANDS = new Set([\"branch\", \"diff\", \"log\", \"rev-parse\", \"show\", \"status\", \"tag\"]);\nconst READ_ONLY_NPM_SUBCOMMANDS = new Set([\"info\", \"list\", \"ls\", \"outdated\", \"view\", \"whoami\"]);\nconst MUTATING_SHELL_TOKEN_RE =\n\t/(^|\\s)(>|>>|2>|&>|tee\\b|rm\\b|mv\\b|cp\\b|mkdir\\b|touch\\b|chmod\\b|chown\\b|install\\b|commit\\b|push\\b|publish\\b|deploy\\b|apply\\b|add\\b|checkout\\b|switch\\b|reset\\b|clean\\b|stash\\b|merge\\b|rebase\\b|npm\\s+(?:i|install|ci|update|publish|run)\\b|pnpm\\s+(?:i|install|update|publish|run)\\b|yarn\\s+(?:add|install|upgrade|publish|run)\\b)/i;\nconst UNSAFE_NESTED_SHELL_EXECUTION_RE = /(`|\\$\\(|\\bfind\\b[\\s\\S]*\\s-exec(?:dir)?\\b|\\bxargs\\b)/i;\nconst MUTATING_TOOL_NAME_RE =\n\t/(bash|exec|execute|run|shell|write|edit|patch|replace|delete|remove|move|rename|create|mkdir|touch|install|commit|push|publish|deploy|apply)/i;\n\nfunction getShellCommand(args: unknown): string | undefined {\n\tif (!args || typeof args !== \"object\") return undefined;\n\tconst record = args as Record<string, unknown>;\n\tconst command = record.command ?? record.cmd ?? record.shellCommand;\n\treturn typeof command === \"string\" ? command.trim() : undefined;\n}\n\nfunction commandName(segment: string): string | undefined {\n\tconst first = segment.trim().match(/^[A-Za-z0-9_./-]+/)?.[0];\n\tif (!first) return undefined;\n\tconst parts = first.split(\"/\");\n\treturn parts[parts.length - 1]?.toLowerCase();\n}\n\nfunction commandArg(segment: string, index: number): string | undefined {\n\treturn segment.trim().split(/\\s+/)[index]?.toLowerCase();\n}\n\nfunction isReadOnlyShellSegment(segment: string): boolean {\n\tconst name = commandName(segment);\n\tif (!name || !READ_ONLY_COMMANDS.has(name)) return false;\n\tif (name === \"git\") {\n\t\tconst subcommand = commandArg(segment, 1);\n\t\treturn Boolean(subcommand && READ_ONLY_GIT_SUBCOMMANDS.has(subcommand));\n\t}\n\tif (name === \"npm\" || name === \"pnpm\" || name === \"yarn\") {\n\t\tconst subcommand = commandArg(segment, 1);\n\t\treturn Boolean(subcommand && READ_ONLY_NPM_SUBCOMMANDS.has(subcommand));\n\t}\n\treturn true;\n}\n\nfunction isReadOnlyShellCommand(command: string): boolean {\n\tif (!command || MUTATING_SHELL_TOKEN_RE.test(command) || UNSAFE_NESTED_SHELL_EXECUTION_RE.test(command))\n\t\treturn false;\n\tconst segments = command.split(/\\s*(?:&&|\\|\\||[;|\\r\\n])\\s*/).map((segment) => segment.trim());\n\treturn segments.length > 0 && segments.every((segment) => segment.length > 0 && isReadOnlyShellSegment(segment));\n}\n\nexport function shouldEscalateModelRouterTool(options: {\n\ttier: ModelTier;\n\ttoolName: string;\n\targs?: unknown;\n\t/** The route's reasonCode; executor-lane turns carry \"executor_direct\". */\n\treasonCode?: string;\n}): boolean {\n\tif (options.tier !== \"cheap\") return false;\n\tconst toolName = options.toolName.trim().toLowerCase();\n\t// Executor-lane turns (G16) exist to run exactly one tool: run_toolkit_script, which enforces\n\t// its own safety (danger confirmation, structural exit-code contract). Escalating on it would\n\t// abort every executor turn at the moment it does its job. Any OTHER mutating tool still\n\t// escalates to the expensive model as usual.\n\tif (options.reasonCode === \"executor_direct\" && toolName === \"run_toolkit_script\") return false;\n\tif (!toolName) return true;\n\tif (READ_ONLY_TOOL_NAMES.has(toolName)) return false;\n\tif (SHELL_TOOL_NAMES.has(toolName)) {\n\t\tconst command = getShellCommand(options.args);\n\t\treturn command ? !isReadOnlyShellCommand(command) : true;\n\t}\n\treturn MUTATING_TOOL_NAME_RE.test(toolName) || !toolName.startsWith(\"read_\");\n}\n"]}
1
+ {"version":3,"file":"tool-escalation.js","sourceRoot":"","sources":["../../../src/core/model-router/tool-escalation.ts"],"names":[],"mappings":"AAEA,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC;IACpC,MAAM;IACN,MAAM;IACN,MAAM;IACN,IAAI;IACJ,MAAM;IACN,QAAQ;IACR,MAAM;IACN,WAAW;IACX,UAAU;IACV,aAAa;IACb,YAAY;IACZ,kBAAkB;IAClB,mBAAmB;CACnB,CAAC,CAAC;AAEH,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC;AAE3G,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC;IAClC,KAAK;IACL,KAAK;IACL,MAAM;IACN,IAAI;IACJ,IAAI;IACJ,KAAK;IACL,aAAa;IACb,cAAc;IACd,eAAe;IACf,aAAa;IACb,aAAa;IACb,UAAU;IACV,cAAc;IACd,aAAa;IACb,KAAK;IACL,MAAM;IACN,MAAM;IACN,IAAI;IACJ,IAAI;IACJ,MAAM;IACN,KAAK;IACL,MAAM;IACN,KAAK;IACL,cAAc;IACd,IAAI;IACJ,KAAK;IACL,eAAe;IACf,eAAe;IACf,MAAM;IACN,MAAM;IACN,WAAW;IACX,KAAK;IACL,IAAI;IACJ,cAAc;IACd,OAAO;IACP,cAAc;IACd,MAAM;CACN,CAAC,CAAC;AAEH,MAAM,yBAAyB,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;AAC3G,MAAM,yBAAyB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;AAChG,MAAM,uBAAuB,GAC5B,scAAsc,CAAC;AACxc,MAAM,gCAAgC,GAAG,sDAAsD,CAAC;AAChG,MAAM,qBAAqB,GAC1B,0JAA0J,CAAC;AAE5J,SAAS,eAAe,CAAC,IAAa,EAAsB;IAC3D,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACxD,MAAM,MAAM,GAAG,IAA+B,CAAC;IAC/C,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,YAAY,CAAC;IACpE,OAAO,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAAA,CAChE;AAED,SAAS,WAAW,CAAC,OAAe,EAAsB;IACzD,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,mBAAmB,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7D,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,OAAO,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;AAAA,CAC9C;AAED,SAAS,UAAU,CAAC,OAAe,EAAE,KAAa,EAAsB;IACvE,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;AAAA,CACzD;AAED,SAAS,sBAAsB,CAAC,OAAe,EAAW;IACzD,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IAClC,IAAI,CAAC,IAAI,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACzD,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QACpB,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAC1C,OAAO,OAAO,CAAC,UAAU,IAAI,yBAAyB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;IACzE,CAAC;IACD,IAAI,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QAC1D,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAC1C,OAAO,OAAO,CAAC,UAAU,IAAI,yBAAyB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,IAAI,CAAC;AAAA,CACZ;AAED,SAAS,sBAAsB,CAAC,OAAe,EAAW;IACzD,IAAI,CAAC,OAAO,IAAI,uBAAuB,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,gCAAgC,CAAC,IAAI,CAAC,OAAO,CAAC;QACtG,OAAO,KAAK,CAAC;IACd,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAC9F,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,sBAAsB,CAAC,OAAO,CAAC,CAAC,CAAC;AAAA,CACjH;AAED,MAAM,UAAU,6BAA6B,CAAC,OAM7C,EAAW;IACX,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IAC3C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACvD,8FAA8F;IAC9F,8FAA8F;IAC9F,yFAAyF;IACzF,6CAA6C;IAC7C,IAAI,OAAO,CAAC,UAAU,KAAK,iBAAiB,IAAI,QAAQ,KAAK,oBAAoB;QAAE,OAAO,KAAK,CAAC;IAChG,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3B,IAAI,oBAAoB,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IACrD,IAAI,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpC,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC9C,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1D,CAAC;IACD,OAAO,qBAAqB,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;AAAA,CAC7E","sourcesContent":["import type { ModelTier } from \"../autonomy/contracts.ts\";\n\nconst READ_ONLY_TOOL_NAMES = new Set([\n\t\"read\",\n\t\"grep\",\n\t\"find\",\n\t\"ls\",\n\t\"list\",\n\t\"search\",\n\t\"glob\",\n\t\"view_file\",\n\t\"list_dir\",\n\t\"grep_search\",\n\t\"search_web\",\n\t\"read_url_content\",\n\t\"read_browser_page\",\n]);\n\nconst SHELL_TOOL_NAMES = new Set([\"bash\", \"powershell\", \"exec\", \"execute\", \"run\", \"run_command\", \"shell\"]);\n\nconst READ_ONLY_COMMANDS = new Set([\n\t\"awk\",\n\t\"cat\",\n\t\"date\",\n\t\"df\",\n\t\"du\",\n\t\"env\",\n\t\"format-list\",\n\t\"format-table\",\n\t\"get-childitem\",\n\t\"get-command\",\n\t\"get-content\",\n\t\"get-item\",\n\t\"get-location\",\n\t\"get-process\",\n\t\"git\",\n\t\"grep\",\n\t\"head\",\n\t\"jq\",\n\t\"ls\",\n\t\"node\",\n\t\"npm\",\n\t\"pnpm\",\n\t\"pwd\",\n\t\"resolve-path\",\n\t\"rg\",\n\t\"sed\",\n\t\"select-object\",\n\t\"select-string\",\n\t\"tail\",\n\t\"test\",\n\t\"test-path\",\n\t\"tsc\",\n\t\"wc\",\n\t\"where-object\",\n\t\"which\",\n\t\"write-output\",\n\t\"yarn\",\n]);\n\nconst READ_ONLY_GIT_SUBCOMMANDS = new Set([\"branch\", \"diff\", \"log\", \"rev-parse\", \"show\", \"status\", \"tag\"]);\nconst READ_ONLY_NPM_SUBCOMMANDS = new Set([\"info\", \"list\", \"ls\", \"outdated\", \"view\", \"whoami\"]);\nconst MUTATING_SHELL_TOKEN_RE =\n\t/(^|\\s)(>|>>|2>|&>|tee\\b|rm\\b|mv\\b|cp\\b|mkdir\\b|touch\\b|chmod\\b|chown\\b|install\\b|commit\\b|push\\b|publish\\b|deploy\\b|apply\\b|add\\b|checkout\\b|switch\\b|reset\\b|clean\\b|stash\\b|merge\\b|rebase\\b|remove-item\\b|move-item\\b|copy-item\\b|new-item\\b|rename-item\\b|set-content\\b|add-content\\b|out-file\\b|set-item\\b|start-process\\b|npm\\s+(?:i|install|ci|update|publish|run)\\b|pnpm\\s+(?:i|install|update|publish|run)\\b|yarn\\s+(?:add|install|upgrade|publish|run)\\b)/i;\nconst UNSAFE_NESTED_SHELL_EXECUTION_RE = /(`|\\$\\(|\\bfind\\b[\\s\\S]*\\s-exec(?:dir)?\\b|\\bxargs\\b)/i;\nconst MUTATING_TOOL_NAME_RE =\n\t/(bash|powershell|exec|execute|run|shell|write|edit|patch|replace|delete|remove|move|rename|create|mkdir|touch|install|commit|push|publish|deploy|apply)/i;\n\nfunction getShellCommand(args: unknown): string | undefined {\n\tif (!args || typeof args !== \"object\") return undefined;\n\tconst record = args as Record<string, unknown>;\n\tconst command = record.command ?? record.cmd ?? record.shellCommand;\n\treturn typeof command === \"string\" ? command.trim() : undefined;\n}\n\nfunction commandName(segment: string): string | undefined {\n\tconst first = segment.trim().match(/^[A-Za-z0-9_./-]+/)?.[0];\n\tif (!first) return undefined;\n\tconst parts = first.split(\"/\");\n\treturn parts[parts.length - 1]?.toLowerCase();\n}\n\nfunction commandArg(segment: string, index: number): string | undefined {\n\treturn segment.trim().split(/\\s+/)[index]?.toLowerCase();\n}\n\nfunction isReadOnlyShellSegment(segment: string): boolean {\n\tconst name = commandName(segment);\n\tif (!name || !READ_ONLY_COMMANDS.has(name)) return false;\n\tif (name === \"git\") {\n\t\tconst subcommand = commandArg(segment, 1);\n\t\treturn Boolean(subcommand && READ_ONLY_GIT_SUBCOMMANDS.has(subcommand));\n\t}\n\tif (name === \"npm\" || name === \"pnpm\" || name === \"yarn\") {\n\t\tconst subcommand = commandArg(segment, 1);\n\t\treturn Boolean(subcommand && READ_ONLY_NPM_SUBCOMMANDS.has(subcommand));\n\t}\n\treturn true;\n}\n\nfunction isReadOnlyShellCommand(command: string): boolean {\n\tif (!command || MUTATING_SHELL_TOKEN_RE.test(command) || UNSAFE_NESTED_SHELL_EXECUTION_RE.test(command))\n\t\treturn false;\n\tconst segments = command.split(/\\s*(?:&&|\\|\\||[;|\\r\\n])\\s*/).map((segment) => segment.trim());\n\treturn segments.length > 0 && segments.every((segment) => segment.length > 0 && isReadOnlyShellSegment(segment));\n}\n\nexport function shouldEscalateModelRouterTool(options: {\n\ttier: ModelTier;\n\ttoolName: string;\n\targs?: unknown;\n\t/** The route's reasonCode; executor-lane turns carry \"executor_direct\". */\n\treasonCode?: string;\n}): boolean {\n\tif (options.tier !== \"cheap\") return false;\n\tconst toolName = options.toolName.trim().toLowerCase();\n\t// Executor-lane turns (G16) exist to run exactly one tool: run_toolkit_script, which enforces\n\t// its own safety (danger confirmation, structural exit-code contract). Escalating on it would\n\t// abort every executor turn at the moment it does its job. Any OTHER mutating tool still\n\t// escalates to the expensive model as usual.\n\tif (options.reasonCode === \"executor_direct\" && toolName === \"run_toolkit_script\") return false;\n\tif (!toolName) return true;\n\tif (READ_ONLY_TOOL_NAMES.has(toolName)) return false;\n\tif (SHELL_TOOL_NAMES.has(toolName)) {\n\t\tconst command = getShellCommand(options.args);\n\t\treturn command ? !isReadOnlyShellCommand(command) : true;\n\t}\n\treturn MUTATING_TOOL_NAME_RE.test(toolName) || !toolName.startsWith(\"read_\");\n}\n"]}
@@ -0,0 +1,46 @@
1
+ export declare const PYTHON_RUNTIME_REQUEST = ">=3.10";
2
+ export declare const PYTHON_RUNTIME_INSTALL_REQUEST = "3.13";
3
+ export declare const PYTHON_RUNTIME_FIND_TIMEOUT_MS = 10000;
4
+ export declare const PYTHON_RUNTIME_INSTALL_TIMEOUT_MS = 300000;
5
+ export interface PythonRuntimeCommandOptions {
6
+ timeoutMs: number;
7
+ env: NodeJS.ProcessEnv;
8
+ }
9
+ export interface PythonRuntimeCommandResult {
10
+ code: number;
11
+ stdout: string;
12
+ stderr: string;
13
+ killed: boolean;
14
+ }
15
+ export interface PythonRuntimeDependencies {
16
+ agentDir: string;
17
+ ensureUv: (silent: boolean) => Promise<string | undefined>;
18
+ isOffline: () => boolean;
19
+ makeDirectory: (path: string) => void;
20
+ pathExists: (path: string) => boolean;
21
+ run: (command: string, args: string[], cwd: string, options: PythonRuntimeCommandOptions) => Promise<PythonRuntimeCommandResult>;
22
+ now: () => number;
23
+ }
24
+ export type PythonRuntimeOutcome = {
25
+ status: "ready";
26
+ uvPath: string;
27
+ pythonPath: string;
28
+ pythonInstalled: boolean;
29
+ } | {
30
+ status: "offline" | "uv-unavailable" | "python-unavailable";
31
+ reason: string;
32
+ };
33
+ export interface PythonRuntimeManager {
34
+ ensure(options?: {
35
+ silent?: boolean;
36
+ force?: boolean;
37
+ }): Promise<PythonRuntimeOutcome>;
38
+ getLastOutcome(): PythonRuntimeOutcome | undefined;
39
+ }
40
+ export declare function createPythonRuntimeManager(deps: PythonRuntimeDependencies): PythonRuntimeManager;
41
+ export declare function ensurePythonRuntime(options?: {
42
+ silent?: boolean;
43
+ force?: boolean;
44
+ }): Promise<PythonRuntimeOutcome>;
45
+ export declare function getLastPythonRuntimeOutcome(): PythonRuntimeOutcome | undefined;
46
+ //# sourceMappingURL=python-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"python-runtime.d.ts","sourceRoot":"","sources":["../../src/core/python-runtime.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,sBAAsB,WAAW,CAAC;AAC/C,eAAO,MAAM,8BAA8B,SAAS,CAAC;AACrD,eAAO,MAAM,8BAA8B,QAAS,CAAC;AACrD,eAAO,MAAM,iCAAiC,SAAU,CAAC;AAKzD,MAAM,WAAW,2BAA2B;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;CACvB;AAED,MAAM,WAAW,0BAA0B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,yBAAyB;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC3D,SAAS,EAAE,MAAM,OAAO,CAAC;IACzB,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IACtC,GAAG,EAAE,CACJ,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,2BAA2B,KAChC,OAAO,CAAC,0BAA0B,CAAC,CAAC;IACzC,GAAG,EAAE,MAAM,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,oBAAoB,GAC7B;IACA,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;CACxB,GACD;IACA,MAAM,EAAE,SAAS,GAAG,gBAAgB,GAAG,oBAAoB,CAAC;IAC5D,MAAM,EAAE,MAAM,CAAC;CACd,CAAC;AAEL,MAAM,WAAW,oBAAoB;IACpC,MAAM,CAAC,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACvF,cAAc,IAAI,oBAAoB,GAAG,SAAS,CAAC;CACnD;AAmBD,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,yBAAyB,GAAG,oBAAoB,CAuGhG;AA0BD,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAElH;AAED,wBAAgB,2BAA2B,IAAI,oBAAoB,GAAG,SAAS,CAE9E","sourcesContent":["import { existsSync, mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../config.ts\";\nimport { ensureTool } from \"../utils/tools-manager.ts\";\nimport { execCommand } from \"./exec.ts\";\n\nexport const PYTHON_RUNTIME_REQUEST = \">=3.10\";\nexport const PYTHON_RUNTIME_INSTALL_REQUEST = \"3.13\";\nexport const PYTHON_RUNTIME_FIND_TIMEOUT_MS = 10_000;\nexport const PYTHON_RUNTIME_INSTALL_TIMEOUT_MS = 300_000;\nconst PYTHON_RUNTIME_FAILURE_COOLDOWN_MS = 30_000;\nconst PYTHON_RUNTIME_DIAGNOSTIC_CHARS = 4_000;\nconst PYTHON_RUNTIME_COMMAND_BUFFER = 64 * 1024;\n\nexport interface PythonRuntimeCommandOptions {\n\ttimeoutMs: number;\n\tenv: NodeJS.ProcessEnv;\n}\n\nexport interface PythonRuntimeCommandResult {\n\tcode: number;\n\tstdout: string;\n\tstderr: string;\n\tkilled: boolean;\n}\n\nexport interface PythonRuntimeDependencies {\n\tagentDir: string;\n\tensureUv: (silent: boolean) => Promise<string | undefined>;\n\tisOffline: () => boolean;\n\tmakeDirectory: (path: string) => void;\n\tpathExists: (path: string) => boolean;\n\trun: (\n\t\tcommand: string,\n\t\targs: string[],\n\t\tcwd: string,\n\t\toptions: PythonRuntimeCommandOptions,\n\t) => Promise<PythonRuntimeCommandResult>;\n\tnow: () => number;\n}\n\nexport type PythonRuntimeOutcome =\n\t| {\n\t\t\tstatus: \"ready\";\n\t\t\tuvPath: string;\n\t\t\tpythonPath: string;\n\t\t\tpythonInstalled: boolean;\n\t }\n\t| {\n\t\t\tstatus: \"offline\" | \"uv-unavailable\" | \"python-unavailable\";\n\t\t\treason: string;\n\t };\n\nexport interface PythonRuntimeManager {\n\tensure(options?: { silent?: boolean; force?: boolean }): Promise<PythonRuntimeOutcome>;\n\tgetLastOutcome(): PythonRuntimeOutcome | undefined;\n}\n\nfunction isTruthyEnvFlag(value: string | undefined): boolean {\n\treturn value === \"1\" || value?.toLowerCase() === \"true\";\n}\n\nfunction boundedDiagnostic(value: string): string {\n\tconst trimmed = value.trim();\n\tif (trimmed.length <= PYTHON_RUNTIME_DIAGNOSTIC_CHARS) return trimmed;\n\treturn `…${trimmed.slice(-PYTHON_RUNTIME_DIAGNOSTIC_CHARS)}`;\n}\n\nfunction firstNonemptyLine(value: string): string | undefined {\n\treturn value\n\t\t.split(/\\r?\\n/u)\n\t\t.map((line) => line.trim())\n\t\t.find(Boolean);\n}\n\nexport function createPythonRuntimeManager(deps: PythonRuntimeDependencies): PythonRuntimeManager {\n\tlet inFlight: Promise<PythonRuntimeOutcome> | undefined;\n\tlet lastOutcome: PythonRuntimeOutcome | undefined;\n\tlet lastOutcomeAt = 0;\n\n\tconst ensureOnce = async (silent: boolean): Promise<PythonRuntimeOutcome> => {\n\t\tconst uvPath = await deps.ensureUv(silent);\n\t\tif (!uvPath) {\n\t\t\treturn { status: \"uv-unavailable\", reason: \"uv is unavailable; run `pi doctor` or reconnect and retry.\" };\n\t\t}\n\n\t\tconst runtimeRoot = join(deps.agentDir, \"runtimes\", \"python\");\n\t\tconst cacheRoot = join(deps.agentDir, \"cache\", \"uv\");\n\t\tdeps.makeDirectory(runtimeRoot);\n\t\tdeps.makeDirectory(cacheRoot);\n\t\tconst env: NodeJS.ProcessEnv = {\n\t\t\t...process.env,\n\t\t\tUV_CACHE_DIR: cacheRoot,\n\t\t\tUV_NO_PROGRESS: \"1\",\n\t\t\tUV_PYTHON_INSTALL_DIR: runtimeRoot,\n\t\t};\n\t\tconst findArgs = [\"python\", \"find\", PYTHON_RUNTIME_REQUEST, \"--no-project\"];\n\t\tconst findPython = () =>\n\t\t\tdeps.run(uvPath, findArgs, deps.agentDir, {\n\t\t\t\ttimeoutMs: PYTHON_RUNTIME_FIND_TIMEOUT_MS,\n\t\t\t\tenv,\n\t\t\t});\n\t\tconst resolveFoundPython = (result: PythonRuntimeCommandResult): PythonRuntimeOutcome | undefined => {\n\t\t\tif (result.code !== 0 || result.killed) return undefined;\n\t\t\tconst pythonPath = firstNonemptyLine(result.stdout);\n\t\t\tif (!pythonPath) {\n\t\t\t\treturn { status: \"python-unavailable\", reason: \"uv reported success without a Python interpreter path.\" };\n\t\t\t}\n\t\t\tif (!deps.pathExists(pythonPath)) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"python-unavailable\",\n\t\t\t\t\treason: `uv reported a Python path that does not exist: ${pythonPath}`,\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn { status: \"ready\", uvPath, pythonPath, pythonInstalled: false };\n\t\t};\n\n\t\tconst initialFind = await findPython();\n\t\tconst initialOutcome = resolveFoundPython(initialFind);\n\t\tif (initialOutcome) return initialOutcome;\n\t\tif (deps.isOffline()) {\n\t\t\treturn {\n\t\t\t\tstatus: \"offline\",\n\t\t\t\treason: \"No Python interpreter is available and offline mode prevents uv from installing one.\",\n\t\t\t};\n\t\t}\n\n\t\tconst install = await deps.run(uvPath, [\"python\", \"install\", PYTHON_RUNTIME_INSTALL_REQUEST], deps.agentDir, {\n\t\t\ttimeoutMs: PYTHON_RUNTIME_INSTALL_TIMEOUT_MS,\n\t\t\tenv,\n\t\t});\n\t\tif (install.code !== 0 || install.killed) {\n\t\t\tconst diagnostic = boundedDiagnostic(install.stderr || install.stdout || \"no diagnostics\");\n\t\t\treturn {\n\t\t\t\tstatus: \"python-unavailable\",\n\t\t\t\treason: `uv python install failed${install.killed ? \" or timed out\" : \"\"}: ${diagnostic}`,\n\t\t\t};\n\t\t}\n\n\t\tconst installedFind = await findPython();\n\t\tconst installedOutcome = resolveFoundPython(installedFind);\n\t\tif (installedOutcome?.status === \"ready\") return { ...installedOutcome, pythonInstalled: true };\n\t\tif (installedOutcome) return installedOutcome;\n\t\tconst diagnostic = boundedDiagnostic(installedFind.stderr || installedFind.stdout || \"no diagnostics\");\n\t\treturn {\n\t\t\tstatus: \"python-unavailable\",\n\t\t\treason: `uv installed Python but could not resolve it: ${diagnostic}`,\n\t\t};\n\t};\n\n\treturn {\n\t\tensure(options = {}) {\n\t\t\tconst force = options.force ?? false;\n\t\t\tif (!force && lastOutcome?.status === \"ready\") return Promise.resolve(lastOutcome);\n\t\t\tif (\n\t\t\t\t!force &&\n\t\t\t\tlastOutcome &&\n\t\t\t\tlastOutcome.status !== \"ready\" &&\n\t\t\t\tdeps.now() - lastOutcomeAt < PYTHON_RUNTIME_FAILURE_COOLDOWN_MS\n\t\t\t) {\n\t\t\t\treturn Promise.resolve(lastOutcome);\n\t\t\t}\n\t\t\tif (inFlight) return inFlight;\n\t\t\tinFlight = ensureOnce(options.silent ?? true)\n\t\t\t\t.then((outcome) => {\n\t\t\t\t\tlastOutcome = outcome;\n\t\t\t\t\tlastOutcomeAt = deps.now();\n\t\t\t\t\treturn outcome;\n\t\t\t\t})\n\t\t\t\t.finally(() => {\n\t\t\t\t\tinFlight = undefined;\n\t\t\t\t});\n\t\t\treturn inFlight;\n\t\t},\n\t\tgetLastOutcome() {\n\t\t\treturn lastOutcome;\n\t\t},\n\t};\n}\n\nconst realPythonRuntimeDependencies: PythonRuntimeDependencies = {\n\tagentDir: getAgentDir(),\n\tensureUv: (silent) => ensureTool(\"uv\", silent),\n\tisOffline: () => isTruthyEnvFlag(process.env.PI_OFFLINE),\n\tmakeDirectory: (path) => mkdirSync(path, { recursive: true, mode: 0o700 }),\n\tpathExists: existsSync,\n\trun: async (command, args, cwd, options) => {\n\t\tconst result = await execCommand(command, args, cwd, {\n\t\t\tenv: options.env,\n\t\t\tmaxBuffer: PYTHON_RUNTIME_COMMAND_BUFFER,\n\t\t\ttimeout: options.timeoutMs,\n\t\t});\n\t\treturn {\n\t\t\tcode: result.code,\n\t\t\tstdout: result.stdout,\n\t\t\tstderr: result.stderr,\n\t\t\tkilled: result.killed,\n\t\t};\n\t},\n\tnow: Date.now,\n};\n\nconst pythonRuntimeManager = createPythonRuntimeManager(realPythonRuntimeDependencies);\n\nexport function ensurePythonRuntime(options?: { silent?: boolean; force?: boolean }): Promise<PythonRuntimeOutcome> {\n\treturn pythonRuntimeManager.ensure(options);\n}\n\nexport function getLastPythonRuntimeOutcome(): PythonRuntimeOutcome | undefined {\n\treturn pythonRuntimeManager.getLastOutcome();\n}\n"]}
@@ -0,0 +1,157 @@
1
+ import { existsSync, mkdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { getAgentDir } from "../config.js";
4
+ import { ensureTool } from "../utils/tools-manager.js";
5
+ import { execCommand } from "./exec.js";
6
+ export const PYTHON_RUNTIME_REQUEST = ">=3.10";
7
+ export const PYTHON_RUNTIME_INSTALL_REQUEST = "3.13";
8
+ export const PYTHON_RUNTIME_FIND_TIMEOUT_MS = 10_000;
9
+ export const PYTHON_RUNTIME_INSTALL_TIMEOUT_MS = 300_000;
10
+ const PYTHON_RUNTIME_FAILURE_COOLDOWN_MS = 30_000;
11
+ const PYTHON_RUNTIME_DIAGNOSTIC_CHARS = 4_000;
12
+ const PYTHON_RUNTIME_COMMAND_BUFFER = 64 * 1024;
13
+ function isTruthyEnvFlag(value) {
14
+ return value === "1" || value?.toLowerCase() === "true";
15
+ }
16
+ function boundedDiagnostic(value) {
17
+ const trimmed = value.trim();
18
+ if (trimmed.length <= PYTHON_RUNTIME_DIAGNOSTIC_CHARS)
19
+ return trimmed;
20
+ return `…${trimmed.slice(-PYTHON_RUNTIME_DIAGNOSTIC_CHARS)}`;
21
+ }
22
+ function firstNonemptyLine(value) {
23
+ return value
24
+ .split(/\r?\n/u)
25
+ .map((line) => line.trim())
26
+ .find(Boolean);
27
+ }
28
+ export function createPythonRuntimeManager(deps) {
29
+ let inFlight;
30
+ let lastOutcome;
31
+ let lastOutcomeAt = 0;
32
+ const ensureOnce = async (silent) => {
33
+ const uvPath = await deps.ensureUv(silent);
34
+ if (!uvPath) {
35
+ return { status: "uv-unavailable", reason: "uv is unavailable; run `pi doctor` or reconnect and retry." };
36
+ }
37
+ const runtimeRoot = join(deps.agentDir, "runtimes", "python");
38
+ const cacheRoot = join(deps.agentDir, "cache", "uv");
39
+ deps.makeDirectory(runtimeRoot);
40
+ deps.makeDirectory(cacheRoot);
41
+ const env = {
42
+ ...process.env,
43
+ UV_CACHE_DIR: cacheRoot,
44
+ UV_NO_PROGRESS: "1",
45
+ UV_PYTHON_INSTALL_DIR: runtimeRoot,
46
+ };
47
+ const findArgs = ["python", "find", PYTHON_RUNTIME_REQUEST, "--no-project"];
48
+ const findPython = () => deps.run(uvPath, findArgs, deps.agentDir, {
49
+ timeoutMs: PYTHON_RUNTIME_FIND_TIMEOUT_MS,
50
+ env,
51
+ });
52
+ const resolveFoundPython = (result) => {
53
+ if (result.code !== 0 || result.killed)
54
+ return undefined;
55
+ const pythonPath = firstNonemptyLine(result.stdout);
56
+ if (!pythonPath) {
57
+ return { status: "python-unavailable", reason: "uv reported success without a Python interpreter path." };
58
+ }
59
+ if (!deps.pathExists(pythonPath)) {
60
+ return {
61
+ status: "python-unavailable",
62
+ reason: `uv reported a Python path that does not exist: ${pythonPath}`,
63
+ };
64
+ }
65
+ return { status: "ready", uvPath, pythonPath, pythonInstalled: false };
66
+ };
67
+ const initialFind = await findPython();
68
+ const initialOutcome = resolveFoundPython(initialFind);
69
+ if (initialOutcome)
70
+ return initialOutcome;
71
+ if (deps.isOffline()) {
72
+ return {
73
+ status: "offline",
74
+ reason: "No Python interpreter is available and offline mode prevents uv from installing one.",
75
+ };
76
+ }
77
+ const install = await deps.run(uvPath, ["python", "install", PYTHON_RUNTIME_INSTALL_REQUEST], deps.agentDir, {
78
+ timeoutMs: PYTHON_RUNTIME_INSTALL_TIMEOUT_MS,
79
+ env,
80
+ });
81
+ if (install.code !== 0 || install.killed) {
82
+ const diagnostic = boundedDiagnostic(install.stderr || install.stdout || "no diagnostics");
83
+ return {
84
+ status: "python-unavailable",
85
+ reason: `uv python install failed${install.killed ? " or timed out" : ""}: ${diagnostic}`,
86
+ };
87
+ }
88
+ const installedFind = await findPython();
89
+ const installedOutcome = resolveFoundPython(installedFind);
90
+ if (installedOutcome?.status === "ready")
91
+ return { ...installedOutcome, pythonInstalled: true };
92
+ if (installedOutcome)
93
+ return installedOutcome;
94
+ const diagnostic = boundedDiagnostic(installedFind.stderr || installedFind.stdout || "no diagnostics");
95
+ return {
96
+ status: "python-unavailable",
97
+ reason: `uv installed Python but could not resolve it: ${diagnostic}`,
98
+ };
99
+ };
100
+ return {
101
+ ensure(options = {}) {
102
+ const force = options.force ?? false;
103
+ if (!force && lastOutcome?.status === "ready")
104
+ return Promise.resolve(lastOutcome);
105
+ if (!force &&
106
+ lastOutcome &&
107
+ lastOutcome.status !== "ready" &&
108
+ deps.now() - lastOutcomeAt < PYTHON_RUNTIME_FAILURE_COOLDOWN_MS) {
109
+ return Promise.resolve(lastOutcome);
110
+ }
111
+ if (inFlight)
112
+ return inFlight;
113
+ inFlight = ensureOnce(options.silent ?? true)
114
+ .then((outcome) => {
115
+ lastOutcome = outcome;
116
+ lastOutcomeAt = deps.now();
117
+ return outcome;
118
+ })
119
+ .finally(() => {
120
+ inFlight = undefined;
121
+ });
122
+ return inFlight;
123
+ },
124
+ getLastOutcome() {
125
+ return lastOutcome;
126
+ },
127
+ };
128
+ }
129
+ const realPythonRuntimeDependencies = {
130
+ agentDir: getAgentDir(),
131
+ ensureUv: (silent) => ensureTool("uv", silent),
132
+ isOffline: () => isTruthyEnvFlag(process.env.PI_OFFLINE),
133
+ makeDirectory: (path) => mkdirSync(path, { recursive: true, mode: 0o700 }),
134
+ pathExists: existsSync,
135
+ run: async (command, args, cwd, options) => {
136
+ const result = await execCommand(command, args, cwd, {
137
+ env: options.env,
138
+ maxBuffer: PYTHON_RUNTIME_COMMAND_BUFFER,
139
+ timeout: options.timeoutMs,
140
+ });
141
+ return {
142
+ code: result.code,
143
+ stdout: result.stdout,
144
+ stderr: result.stderr,
145
+ killed: result.killed,
146
+ };
147
+ },
148
+ now: Date.now,
149
+ };
150
+ const pythonRuntimeManager = createPythonRuntimeManager(realPythonRuntimeDependencies);
151
+ export function ensurePythonRuntime(options) {
152
+ return pythonRuntimeManager.ensure(options);
153
+ }
154
+ export function getLastPythonRuntimeOutcome() {
155
+ return pythonRuntimeManager.getLastOutcome();
156
+ }
157
+ //# sourceMappingURL=python-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"python-runtime.js","sourceRoot":"","sources":["../../src/core/python-runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../config.ts\";\nimport { ensureTool } from \"../utils/tools-manager.ts\";\nimport { execCommand } from \"./exec.ts\";\n\nexport const PYTHON_RUNTIME_REQUEST = \">=3.10\";\nexport const PYTHON_RUNTIME_INSTALL_REQUEST = \"3.13\";\nexport const PYTHON_RUNTIME_FIND_TIMEOUT_MS = 10_000;\nexport const PYTHON_RUNTIME_INSTALL_TIMEOUT_MS = 300_000;\nconst PYTHON_RUNTIME_FAILURE_COOLDOWN_MS = 30_000;\nconst PYTHON_RUNTIME_DIAGNOSTIC_CHARS = 4_000;\nconst PYTHON_RUNTIME_COMMAND_BUFFER = 64 * 1024;\n\nexport interface PythonRuntimeCommandOptions {\n\ttimeoutMs: number;\n\tenv: NodeJS.ProcessEnv;\n}\n\nexport interface PythonRuntimeCommandResult {\n\tcode: number;\n\tstdout: string;\n\tstderr: string;\n\tkilled: boolean;\n}\n\nexport interface PythonRuntimeDependencies {\n\tagentDir: string;\n\tensureUv: (silent: boolean) => Promise<string | undefined>;\n\tisOffline: () => boolean;\n\tmakeDirectory: (path: string) => void;\n\tpathExists: (path: string) => boolean;\n\trun: (\n\t\tcommand: string,\n\t\targs: string[],\n\t\tcwd: string,\n\t\toptions: PythonRuntimeCommandOptions,\n\t) => Promise<PythonRuntimeCommandResult>;\n\tnow: () => number;\n}\n\nexport type PythonRuntimeOutcome =\n\t| {\n\t\t\tstatus: \"ready\";\n\t\t\tuvPath: string;\n\t\t\tpythonPath: string;\n\t\t\tpythonInstalled: boolean;\n\t }\n\t| {\n\t\t\tstatus: \"offline\" | \"uv-unavailable\" | \"python-unavailable\";\n\t\t\treason: string;\n\t };\n\nexport interface PythonRuntimeManager {\n\tensure(options?: { silent?: boolean; force?: boolean }): Promise<PythonRuntimeOutcome>;\n\tgetLastOutcome(): PythonRuntimeOutcome | undefined;\n}\n\nfunction isTruthyEnvFlag(value: string | undefined): boolean {\n\treturn value === \"1\" || value?.toLowerCase() === \"true\";\n}\n\nfunction boundedDiagnostic(value: string): string {\n\tconst trimmed = value.trim();\n\tif (trimmed.length <= PYTHON_RUNTIME_DIAGNOSTIC_CHARS) return trimmed;\n\treturn `…${trimmed.slice(-PYTHON_RUNTIME_DIAGNOSTIC_CHARS)}`;\n}\n\nfunction firstNonemptyLine(value: string): string | undefined {\n\treturn value\n\t\t.split(/\\r?\\n/u)\n\t\t.map((line) => line.trim())\n\t\t.find(Boolean);\n}\n\nexport function createPythonRuntimeManager(deps: PythonRuntimeDependencies): PythonRuntimeManager {\n\tlet inFlight: Promise<PythonRuntimeOutcome> | undefined;\n\tlet lastOutcome: PythonRuntimeOutcome | undefined;\n\tlet lastOutcomeAt = 0;\n\n\tconst ensureOnce = async (silent: boolean): Promise<PythonRuntimeOutcome> => {\n\t\tconst uvPath = await deps.ensureUv(silent);\n\t\tif (!uvPath) {\n\t\t\treturn { status: \"uv-unavailable\", reason: \"uv is unavailable; run `pi doctor` or reconnect and retry.\" };\n\t\t}\n\n\t\tconst runtimeRoot = join(deps.agentDir, \"runtimes\", \"python\");\n\t\tconst cacheRoot = join(deps.agentDir, \"cache\", \"uv\");\n\t\tdeps.makeDirectory(runtimeRoot);\n\t\tdeps.makeDirectory(cacheRoot);\n\t\tconst env: NodeJS.ProcessEnv = {\n\t\t\t...process.env,\n\t\t\tUV_CACHE_DIR: cacheRoot,\n\t\t\tUV_NO_PROGRESS: \"1\",\n\t\t\tUV_PYTHON_INSTALL_DIR: runtimeRoot,\n\t\t};\n\t\tconst findArgs = [\"python\", \"find\", PYTHON_RUNTIME_REQUEST, \"--no-project\"];\n\t\tconst findPython = () =>\n\t\t\tdeps.run(uvPath, findArgs, deps.agentDir, {\n\t\t\t\ttimeoutMs: PYTHON_RUNTIME_FIND_TIMEOUT_MS,\n\t\t\t\tenv,\n\t\t\t});\n\t\tconst resolveFoundPython = (result: PythonRuntimeCommandResult): PythonRuntimeOutcome | undefined => {\n\t\t\tif (result.code !== 0 || result.killed) return undefined;\n\t\t\tconst pythonPath = firstNonemptyLine(result.stdout);\n\t\t\tif (!pythonPath) {\n\t\t\t\treturn { status: \"python-unavailable\", reason: \"uv reported success without a Python interpreter path.\" };\n\t\t\t}\n\t\t\tif (!deps.pathExists(pythonPath)) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"python-unavailable\",\n\t\t\t\t\treason: `uv reported a Python path that does not exist: ${pythonPath}`,\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn { status: \"ready\", uvPath, pythonPath, pythonInstalled: false };\n\t\t};\n\n\t\tconst initialFind = await findPython();\n\t\tconst initialOutcome = resolveFoundPython(initialFind);\n\t\tif (initialOutcome) return initialOutcome;\n\t\tif (deps.isOffline()) {\n\t\t\treturn {\n\t\t\t\tstatus: \"offline\",\n\t\t\t\treason: \"No Python interpreter is available and offline mode prevents uv from installing one.\",\n\t\t\t};\n\t\t}\n\n\t\tconst install = await deps.run(uvPath, [\"python\", \"install\", PYTHON_RUNTIME_INSTALL_REQUEST], deps.agentDir, {\n\t\t\ttimeoutMs: PYTHON_RUNTIME_INSTALL_TIMEOUT_MS,\n\t\t\tenv,\n\t\t});\n\t\tif (install.code !== 0 || install.killed) {\n\t\t\tconst diagnostic = boundedDiagnostic(install.stderr || install.stdout || \"no diagnostics\");\n\t\t\treturn {\n\t\t\t\tstatus: \"python-unavailable\",\n\t\t\t\treason: `uv python install failed${install.killed ? \" or timed out\" : \"\"}: ${diagnostic}`,\n\t\t\t};\n\t\t}\n\n\t\tconst installedFind = await findPython();\n\t\tconst installedOutcome = resolveFoundPython(installedFind);\n\t\tif (installedOutcome?.status === \"ready\") return { ...installedOutcome, pythonInstalled: true };\n\t\tif (installedOutcome) return installedOutcome;\n\t\tconst diagnostic = boundedDiagnostic(installedFind.stderr || installedFind.stdout || \"no diagnostics\");\n\t\treturn {\n\t\t\tstatus: \"python-unavailable\",\n\t\t\treason: `uv installed Python but could not resolve it: ${diagnostic}`,\n\t\t};\n\t};\n\n\treturn {\n\t\tensure(options = {}) {\n\t\t\tconst force = options.force ?? false;\n\t\t\tif (!force && lastOutcome?.status === \"ready\") return Promise.resolve(lastOutcome);\n\t\t\tif (\n\t\t\t\t!force &&\n\t\t\t\tlastOutcome &&\n\t\t\t\tlastOutcome.status !== \"ready\" &&\n\t\t\t\tdeps.now() - lastOutcomeAt < PYTHON_RUNTIME_FAILURE_COOLDOWN_MS\n\t\t\t) {\n\t\t\t\treturn Promise.resolve(lastOutcome);\n\t\t\t}\n\t\t\tif (inFlight) return inFlight;\n\t\t\tinFlight = ensureOnce(options.silent ?? true)\n\t\t\t\t.then((outcome) => {\n\t\t\t\t\tlastOutcome = outcome;\n\t\t\t\t\tlastOutcomeAt = deps.now();\n\t\t\t\t\treturn outcome;\n\t\t\t\t})\n\t\t\t\t.finally(() => {\n\t\t\t\t\tinFlight = undefined;\n\t\t\t\t});\n\t\t\treturn inFlight;\n\t\t},\n\t\tgetLastOutcome() {\n\t\t\treturn lastOutcome;\n\t\t},\n\t};\n}\n\nconst realPythonRuntimeDependencies: PythonRuntimeDependencies = {\n\tagentDir: getAgentDir(),\n\tensureUv: (silent) => ensureTool(\"uv\", silent),\n\tisOffline: () => isTruthyEnvFlag(process.env.PI_OFFLINE),\n\tmakeDirectory: (path) => mkdirSync(path, { recursive: true, mode: 0o700 }),\n\tpathExists: existsSync,\n\trun: async (command, args, cwd, options) => {\n\t\tconst result = await execCommand(command, args, cwd, {\n\t\t\tenv: options.env,\n\t\t\tmaxBuffer: PYTHON_RUNTIME_COMMAND_BUFFER,\n\t\t\ttimeout: options.timeoutMs,\n\t\t});\n\t\treturn {\n\t\t\tcode: result.code,\n\t\t\tstdout: result.stdout,\n\t\t\tstderr: result.stderr,\n\t\t\tkilled: result.killed,\n\t\t};\n\t},\n\tnow: Date.now,\n};\n\nconst pythonRuntimeManager = createPythonRuntimeManager(realPythonRuntimeDependencies);\n\nexport function ensurePythonRuntime(options?: { silent?: boolean; force?: boolean }): Promise<PythonRuntimeOutcome> {\n\treturn pythonRuntimeManager.ensure(options);\n}\n\nexport function getLastPythonRuntimeOutcome(): PythonRuntimeOutcome | undefined {\n\treturn pythonRuntimeManager.getLastOutcome();\n}\n"]}
@@ -50,6 +50,7 @@ import type { ProfileFilterReloadSnapshot } from "./profile-filter-controller.ts
50
50
  import type { ModelFitnessReport } from "./research/model-fitness.ts";
51
51
  import type { ResourceLoader } from "./resource-loader.ts";
52
52
  import { type ResourceProfileFilterSettings, type SettingsManager } from "./settings-manager.ts";
53
+ import type { TaskStepsState } from "./tasks/task-state.ts";
53
54
  export interface RuntimeBuilderDeps {
54
55
  /** Live agent — the snapshot/doctor read and restore its `state.tools` / `state.systemPrompt`. */
55
56
  getAgent(): Agent;
@@ -132,6 +133,9 @@ export interface RuntimeBuilderDeps {
132
133
  /** Goal-tool state accessors. */
133
134
  getGoalStateSnapshot(): GoalState | undefined;
134
135
  saveGoalStateSnapshot(state: GoalState): string;
136
+ /** Native task-step state accessors. */
137
+ getTaskStepsStateSnapshot(): TaskStepsState | undefined;
138
+ saveTaskStepsStateSnapshot(state: TaskStepsState): string;
135
139
  /** Context-gc report for the core diagnostics tool. */
136
140
  getContextGcReport(messages: AgentMessage[]): ContextGcReport;
137
141
  /** Non-blocking worker-delegation starter for the delegate tool. */