@gajae-code/coding-agent 0.5.4 → 0.6.1
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.
- package/CHANGELOG.md +23 -0
- package/dist/types/cli/web-search-cli.d.ts +12 -0
- package/dist/types/commands/rlm.d.ts +10 -0
- package/dist/types/commands/web-search.d.ts +54 -0
- package/dist/types/config/keybindings.d.ts +10 -0
- package/dist/types/config/model-profiles.d.ts +2 -1
- package/dist/types/config/model-registry.d.ts +3 -0
- package/dist/types/config/models-config-schema.d.ts +3 -0
- package/dist/types/config/settings-schema.d.ts +61 -3
- package/dist/types/edit/notebook.d.ts +3 -0
- package/dist/types/eval/py/executor.d.ts +3 -0
- package/dist/types/eval/py/kernel.d.ts +3 -1
- package/dist/types/eval/py/runtime.d.ts +9 -1
- package/dist/types/exec/bash-executor.d.ts +4 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -0
- package/dist/types/extensibility/custom-tools/wrapper.d.ts +1 -0
- package/dist/types/extensibility/extensions/types.d.ts +2 -0
- package/dist/types/extensibility/extensions/wrapper.d.ts +1 -0
- package/dist/types/gjc-runtime/launch-tmux.d.ts +6 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +14 -0
- package/dist/types/gjc-runtime/tmux-common.d.ts +6 -0
- package/dist/types/gjc-runtime/tmux-gc.d.ts +3 -3
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +4 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +18 -0
- package/dist/types/goals/state.d.ts +1 -1
- package/dist/types/goals/tools/goal-tool.d.ts +2 -0
- package/dist/types/main.d.ts +11 -0
- package/dist/types/modes/components/custom-editor.d.ts +4 -2
- package/dist/types/modes/components/custom-model-preset-wizard.d.ts +12 -0
- package/dist/types/modes/components/model-selector.d.ts +5 -2
- package/dist/types/modes/components/status-line.d.ts +4 -1
- package/dist/types/modes/controllers/input-controller.d.ts +3 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/print-mode.d.ts +6 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +21 -0
- package/dist/types/modes/rpc/rpc-socket-security.d.ts +7 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +13 -0
- package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +2 -0
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +1 -0
- package/dist/types/rlm/artifacts.d.ts +9 -0
- package/dist/types/rlm/complete-research-tool.d.ts +35 -0
- package/dist/types/rlm/data-context.d.ts +6 -0
- package/dist/types/rlm/index.d.ts +35 -0
- package/dist/types/rlm/notebook.d.ts +12 -0
- package/dist/types/rlm/preset.d.ts +23 -0
- package/dist/types/rlm/python-tool.d.ts +16 -0
- package/dist/types/rlm/report.d.ts +14 -0
- package/dist/types/rlm/types.d.ts +37 -0
- package/dist/types/sdk.d.ts +7 -0
- package/dist/types/session/agent-session.d.ts +21 -0
- package/dist/types/tools/bash-allowed-prefixes.d.ts +6 -1
- package/dist/types/tools/browser/attach.d.ts +19 -3
- package/dist/types/tools/browser/registry.d.ts +15 -0
- package/dist/types/tools/browser/render.d.ts +3 -0
- package/dist/types/tools/browser.d.ts +18 -1
- package/dist/types/tools/computer/render.d.ts +17 -0
- package/dist/types/tools/computer.d.ts +465 -0
- package/dist/types/tools/index.d.ts +24 -1
- package/dist/types/tools/job.d.ts +13 -0
- package/dist/types/tools/tool-timeouts.d.ts +5 -0
- package/dist/types/web/search/index.d.ts +32 -2
- package/dist/types/web/search/providers/base.d.ts +22 -0
- package/dist/types/web/search/providers/xai.d.ts +64 -0
- package/dist/types/web/search/types.d.ts +11 -3
- package/package.json +7 -7
- package/src/cli/web-search-cli.ts +123 -8
- package/src/cli.ts +2 -0
- package/src/commands/rlm.ts +19 -0
- package/src/commands/web-search.ts +66 -0
- package/src/config/keybindings.ts +11 -0
- package/src/config/model-profiles.ts +11 -3
- package/src/config/model-registry.ts +55 -1
- package/src/config/models-config-schema.ts +1 -0
- package/src/config/settings-schema.ts +67 -1
- package/src/edit/notebook.ts +6 -2
- package/src/eval/py/executor.ts +8 -1
- package/src/eval/py/kernel.ts +9 -4
- package/src/eval/py/runtime.ts +153 -32
- package/src/exec/bash-executor.ts +10 -4
- package/src/extensibility/custom-tools/types.ts +2 -0
- package/src/extensibility/custom-tools/wrapper.ts +2 -0
- package/src/extensibility/extensions/types.ts +2 -0
- package/src/extensibility/extensions/wrapper.ts +1 -0
- package/src/gjc-runtime/launch-tmux.ts +129 -1
- package/src/gjc-runtime/session-state-sidecar.ts +61 -1
- package/src/gjc-runtime/tmux-common.ts +26 -2
- package/src/gjc-runtime/tmux-gc.ts +40 -27
- package/src/gjc-runtime/tmux-sessions.ts +13 -1
- package/src/gjc-runtime/ultragoal-runtime.ts +340 -18
- package/src/goals/runtime.ts +4 -3
- package/src/goals/state.ts +1 -1
- package/src/goals/tools/goal-tool.ts +16 -3
- package/src/internal-urls/docs-index.generated.ts +13 -9
- package/src/main.ts +28 -3
- package/src/modes/components/custom-editor.ts +13 -4
- package/src/modes/components/custom-model-preset-wizard.ts +293 -0
- package/src/modes/components/hook-selector.ts +1 -1
- package/src/modes/components/model-selector.ts +72 -29
- package/src/modes/components/skill-message.ts +62 -8
- package/src/modes/components/status-line.ts +13 -1
- package/src/modes/controllers/input-controller.ts +60 -11
- package/src/modes/controllers/selector-controller.ts +39 -0
- package/src/modes/interactive-mode.ts +1 -1
- package/src/modes/print-mode.ts +14 -4
- package/src/modes/rpc/rpc-client.ts +250 -80
- package/src/modes/rpc/rpc-mode.ts +6 -12
- package/src/modes/rpc/rpc-socket-security.ts +103 -0
- package/src/modes/rpc/rpc-types.ts +10 -0
- package/src/modes/shared/agent-wire/command-dispatch.ts +7 -0
- package/src/modes/shared/agent-wire/command-validation.ts +1 -0
- package/src/modes/shared/agent-wire/scopes.ts +1 -0
- package/src/modes/shared/agent-wire/unattended-session.ts +9 -0
- package/src/modes/utils/hotkeys-markdown.ts +4 -2
- package/src/modes/utils/ui-helpers.ts +2 -2
- package/src/prompts/goals/goal-continuation.md +1 -0
- package/src/prompts/goals/goal-mode-active.md +1 -0
- package/src/prompts/system/rlm-report-command.md +1 -0
- package/src/prompts/system/rlm-research.md +23 -0
- package/src/prompts/tools/bash.md +23 -2
- package/src/prompts/tools/browser.md +7 -3
- package/src/prompts/tools/computer.md +74 -0
- package/src/prompts/tools/goal.md +3 -0
- package/src/prompts/tools/job.md +9 -1
- package/src/prompts/tools/web-search.md +7 -0
- package/src/rlm/artifacts.ts +60 -0
- package/src/rlm/complete-research-tool.ts +163 -0
- package/src/rlm/data-context.ts +26 -0
- package/src/rlm/index.ts +339 -0
- package/src/rlm/notebook.ts +108 -0
- package/src/rlm/preset.ts +76 -0
- package/src/rlm/python-tool.ts +68 -0
- package/src/rlm/report.ts +70 -0
- package/src/rlm/types.ts +40 -0
- package/src/sdk.ts +12 -0
- package/src/session/agent-session.ts +48 -3
- package/src/slash-commands/builtin-registry.ts +17 -0
- package/src/tools/bash-allowed-prefixes.ts +84 -1
- package/src/tools/bash.ts +80 -13
- package/src/tools/browser/attach.ts +103 -3
- package/src/tools/browser/registry.ts +176 -2
- package/src/tools/browser/render.ts +9 -1
- package/src/tools/browser.ts +33 -0
- package/src/tools/computer/render.ts +78 -0
- package/src/tools/computer.ts +640 -0
- package/src/tools/index.ts +41 -1
- package/src/tools/job.ts +88 -5
- package/src/tools/json-tree.ts +42 -29
- package/src/tools/renderers.ts +2 -0
- package/src/tools/tool-timeouts.ts +1 -0
- package/src/web/search/index.ts +27 -2
- package/src/web/search/provider.ts +16 -1
- package/src/web/search/providers/base.ts +22 -0
- package/src/web/search/providers/xai.ts +511 -0
- package/src/web/search/render.ts +7 -0
- package/src/web/search/types.ts +11 -1
|
@@ -1000,7 +1000,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
1000
1000
|
tab: "interaction",
|
|
1001
1001
|
label: "Busy Prompt Mode",
|
|
1002
1002
|
description:
|
|
1003
|
-
"What a submitted prompt does while the agent is busy:
|
|
1003
|
+
"What a submitted prompt does while the agent is busy: queue normal chat for the next turn, or steer to interrupt the active turn",
|
|
1004
1004
|
},
|
|
1005
1005
|
},
|
|
1006
1006
|
|
|
@@ -2160,6 +2160,66 @@ export const SETTINGS_SCHEMA = {
|
|
|
2160
2160
|
},
|
|
2161
2161
|
},
|
|
2162
2162
|
|
|
2163
|
+
"computer.enabled": {
|
|
2164
|
+
type: "boolean",
|
|
2165
|
+
default: false,
|
|
2166
|
+
ui: {
|
|
2167
|
+
tab: "tools",
|
|
2168
|
+
label: "Computer",
|
|
2169
|
+
description: "Enable the macOS computer tool for this session. Off by default.",
|
|
2170
|
+
},
|
|
2171
|
+
},
|
|
2172
|
+
|
|
2173
|
+
"computer.alwaysOn": {
|
|
2174
|
+
type: "boolean",
|
|
2175
|
+
default: false,
|
|
2176
|
+
ui: {
|
|
2177
|
+
tab: "tools",
|
|
2178
|
+
label: "Computer Always On",
|
|
2179
|
+
description: "Keep the macOS computer tool callable without per-session enablement.",
|
|
2180
|
+
},
|
|
2181
|
+
},
|
|
2182
|
+
|
|
2183
|
+
"computer.autoScreenshot": {
|
|
2184
|
+
type: "boolean",
|
|
2185
|
+
default: false,
|
|
2186
|
+
ui: {
|
|
2187
|
+
tab: "tools",
|
|
2188
|
+
label: "Computer Auto Screenshot",
|
|
2189
|
+
description: "Automatically request bounded screenshots after computer actions when supported.",
|
|
2190
|
+
},
|
|
2191
|
+
},
|
|
2192
|
+
|
|
2193
|
+
"computer.screenshotMaxBytes": {
|
|
2194
|
+
type: "number",
|
|
2195
|
+
default: 5_000_000,
|
|
2196
|
+
ui: {
|
|
2197
|
+
tab: "tools",
|
|
2198
|
+
label: "Computer Screenshot Max Bytes",
|
|
2199
|
+
description: "Maximum screenshot payload size for computer action results.",
|
|
2200
|
+
},
|
|
2201
|
+
},
|
|
2202
|
+
|
|
2203
|
+
"computer.killSwitchHotkey": {
|
|
2204
|
+
type: "string",
|
|
2205
|
+
default: "Control+Option+Command+Escape",
|
|
2206
|
+
ui: {
|
|
2207
|
+
tab: "tools",
|
|
2208
|
+
label: "Computer Kill Switch Hotkey",
|
|
2209
|
+
description: "Native stop/suspend hotkey shown to users for computer-use sessions.",
|
|
2210
|
+
},
|
|
2211
|
+
},
|
|
2212
|
+
|
|
2213
|
+
"computer.auditLog.enabled": {
|
|
2214
|
+
type: "boolean",
|
|
2215
|
+
default: true,
|
|
2216
|
+
ui: {
|
|
2217
|
+
tab: "tools",
|
|
2218
|
+
label: "Computer Audit Log",
|
|
2219
|
+
description: "Persist audit records for enabled computer-use actions.",
|
|
2220
|
+
},
|
|
2221
|
+
},
|
|
2222
|
+
|
|
2163
2223
|
// Tool execution
|
|
2164
2224
|
"tools.intentTracing": {
|
|
2165
2225
|
type: "boolean",
|
|
@@ -2647,6 +2707,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
2647
2707
|
"anthropic",
|
|
2648
2708
|
"gemini",
|
|
2649
2709
|
"codex",
|
|
2710
|
+
"xai",
|
|
2650
2711
|
"tavily",
|
|
2651
2712
|
"kagi",
|
|
2652
2713
|
"synthetic",
|
|
@@ -2688,6 +2749,11 @@ export const SETTINGS_SCHEMA = {
|
|
|
2688
2749
|
label: "OpenAI",
|
|
2689
2750
|
description: "OpenAI's native web_search (uses ChatGPT OAuth via /login openai-codex)",
|
|
2690
2751
|
},
|
|
2752
|
+
{
|
|
2753
|
+
value: "xai",
|
|
2754
|
+
label: "xAI",
|
|
2755
|
+
description: "xAI Responses web_search/x_search (uses xAI OAuth via /login xai or XAI_API_KEY)",
|
|
2756
|
+
},
|
|
2691
2757
|
{
|
|
2692
2758
|
value: "gemini",
|
|
2693
2759
|
label: "Gemini",
|
package/src/edit/notebook.ts
CHANGED
|
@@ -49,7 +49,7 @@ function cloneCell(cell: NotebookCell): NotebookCell {
|
|
|
49
49
|
return structuredClone(cell);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
function createNotebookCell(cellType: NotebookCellType, source: string): NotebookCell {
|
|
52
|
+
export function createNotebookCell(cellType: NotebookCellType, source: string): NotebookCell {
|
|
53
53
|
const cell: NotebookCell = {
|
|
54
54
|
cell_type: cellType,
|
|
55
55
|
metadata: {},
|
|
@@ -62,7 +62,7 @@ function createNotebookCell(cellType: NotebookCellType, source: string): Noteboo
|
|
|
62
62
|
return cell;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
function createEmptyNotebook(): NotebookDocument {
|
|
65
|
+
export function createEmptyNotebook(): NotebookDocument {
|
|
66
66
|
return {
|
|
67
67
|
cells: [],
|
|
68
68
|
metadata: {},
|
|
@@ -71,6 +71,10 @@ function createEmptyNotebook(): NotebookDocument {
|
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
export function serializeNotebookDocument(notebook: NotebookDocument): string {
|
|
75
|
+
return JSON.stringify(notebook, null, 1);
|
|
76
|
+
}
|
|
77
|
+
|
|
74
78
|
function validateNotebook(value: unknown, displayPath: string): NotebookDocument {
|
|
75
79
|
if (!isRecord(value)) {
|
|
76
80
|
throw new Error(`Invalid notebook structure (expected object): ${displayPath}`);
|
package/src/eval/py/executor.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type KernelExecuteResult,
|
|
13
13
|
PythonKernel,
|
|
14
14
|
} from "./kernel";
|
|
15
|
+
import type { PythonRuntimeOptions } from "./runtime";
|
|
15
16
|
import { ensurePyToolBridge, registerPyToolBridge } from "./tool-bridge";
|
|
16
17
|
|
|
17
18
|
export type PythonKernelMode = "session" | "per-call";
|
|
@@ -44,6 +45,8 @@ export interface PythonExecutorOptions {
|
|
|
44
45
|
* preferred over `PI_SESSION_FILE`-derived paths.
|
|
45
46
|
*/
|
|
46
47
|
artifactsDir?: string;
|
|
48
|
+
/** Runtime resolution/provisioning options (RLM managed workspace venv, package seeding). */
|
|
49
|
+
runtimeOptions?: PythonRuntimeOptions;
|
|
47
50
|
/** Artifact path/id for full output storage */
|
|
48
51
|
artifactPath?: string;
|
|
49
52
|
artifactId?: string;
|
|
@@ -276,6 +279,7 @@ async function startKernel(cwd: string, options: PythonExecutorOptions): Promise
|
|
|
276
279
|
return await PythonKernel.start({
|
|
277
280
|
cwd,
|
|
278
281
|
env: buildKernelEnv(options),
|
|
282
|
+
runtimeOptions: options.runtimeOptions,
|
|
279
283
|
signal: options.signal,
|
|
280
284
|
deadlineMs: options.deadlineMs,
|
|
281
285
|
});
|
|
@@ -562,7 +566,10 @@ async function executeWithKernel(
|
|
|
562
566
|
}
|
|
563
567
|
|
|
564
568
|
async function ensureKernelAvailable(cwd: string, options: PythonExecutorOptions): Promise<void> {
|
|
565
|
-
const availability = await waitForPromiseWithCancellation(
|
|
569
|
+
const availability = await waitForPromiseWithCancellation(
|
|
570
|
+
checkPythonKernelAvailability(cwd, options.runtimeOptions),
|
|
571
|
+
options,
|
|
572
|
+
);
|
|
566
573
|
if (!availability.ok) {
|
|
567
574
|
throw new Error(availability.reason ?? "Python kernel unavailable");
|
|
568
575
|
}
|
package/src/eval/py/kernel.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { Settings } from "../../config/settings";
|
|
|
18
18
|
import { type KernelDisplayOutput, renderKernelDisplay } from "./display";
|
|
19
19
|
import { PYTHON_PRELUDE } from "./prelude";
|
|
20
20
|
import RUNNER_SCRIPT from "./runner.py" with { type: "text" };
|
|
21
|
-
import { filterEnv,
|
|
21
|
+
import { ensurePythonRuntime, filterEnv, type PythonRuntimeOptions } from "./runtime";
|
|
22
22
|
|
|
23
23
|
export type { KernelDisplayOutput, PythonStatusEvent } from "./display";
|
|
24
24
|
export { renderKernelDisplay } from "./display";
|
|
@@ -89,6 +89,7 @@ interface KernelLifecycleOptions {
|
|
|
89
89
|
interface KernelStartOptions extends KernelLifecycleOptions {
|
|
90
90
|
cwd: string;
|
|
91
91
|
env?: Record<string, string | undefined>;
|
|
92
|
+
runtimeOptions?: PythonRuntimeOptions;
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
interface KernelShutdownOptions {
|
|
@@ -120,7 +121,10 @@ function throwIfAborted(signal: AbortSignal | undefined, fallbackReason: string)
|
|
|
120
121
|
throw createAbortError("AbortError", typeof reason === "string" ? reason : fallbackReason);
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
export async function checkPythonKernelAvailability(
|
|
124
|
+
export async function checkPythonKernelAvailability(
|
|
125
|
+
cwd: string,
|
|
126
|
+
runtimeOptions?: PythonRuntimeOptions,
|
|
127
|
+
): Promise<PythonKernelAvailability> {
|
|
124
128
|
if (isBunTestRuntime() || $flag("PI_PYTHON_SKIP_CHECK")) {
|
|
125
129
|
return { ok: true };
|
|
126
130
|
}
|
|
@@ -128,7 +132,7 @@ export async function checkPythonKernelAvailability(cwd: string): Promise<Python
|
|
|
128
132
|
const settings = await Settings.init();
|
|
129
133
|
const { env } = settings.getShellConfig();
|
|
130
134
|
const baseEnv = filterEnv(env);
|
|
131
|
-
const runtime =
|
|
135
|
+
const runtime = await ensurePythonRuntime(cwd, baseEnv, runtimeOptions);
|
|
132
136
|
const probe = await $`${runtime.pythonPath} -c "import sys;sys.exit(0)"`
|
|
133
137
|
.quiet()
|
|
134
138
|
.nothrow()
|
|
@@ -199,6 +203,7 @@ export class PythonKernel {
|
|
|
199
203
|
"PythonKernel.start:availabilityCheck",
|
|
200
204
|
checkPythonKernelAvailability,
|
|
201
205
|
options.cwd,
|
|
206
|
+
options.runtimeOptions,
|
|
202
207
|
);
|
|
203
208
|
if (!availability.ok) {
|
|
204
209
|
throw new Error(availability.reason ?? "Python kernel unavailable");
|
|
@@ -207,7 +212,7 @@ export class PythonKernel {
|
|
|
207
212
|
const settings = await Settings.init();
|
|
208
213
|
const { env: shellEnv } = settings.getShellConfig();
|
|
209
214
|
const baseEnv = filterEnv(shellEnv);
|
|
210
|
-
const runtime =
|
|
215
|
+
const runtime = await ensurePythonRuntime(options.cwd, baseEnv, options.runtimeOptions);
|
|
211
216
|
const spawnEnv: Record<string, string> = {};
|
|
212
217
|
for (const [key, value] of Object.entries(runtime.env)) {
|
|
213
218
|
if (typeof value === "string") spawnEnv[key] = value;
|
package/src/eval/py/runtime.ts
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Python runtime resolution utilities.
|
|
3
3
|
*
|
|
4
|
-
* Centralizes environment filtering, venv detection,
|
|
5
|
-
* for both the shared gateway
|
|
4
|
+
* Centralizes environment filtering, venv detection, managed workspace venv
|
|
5
|
+
* provisioning, and Python executable resolution for both the shared gateway
|
|
6
|
+
* and local kernel spawning.
|
|
6
7
|
*/
|
|
7
8
|
import * as fs from "node:fs";
|
|
8
9
|
import * as path from "node:path";
|
|
9
10
|
import { $env, $which, getPythonEnvDir } from "@gajae-code/utils";
|
|
10
11
|
|
|
12
|
+
export const RLM_MANAGED_PYTHON_PACKAGES: readonly string[] = ["numpy", "pandas", "matplotlib", "polars"];
|
|
13
|
+
|
|
14
|
+
export interface PythonRuntimeOptions {
|
|
15
|
+
/** Create/use <cwd>/.gjc/python-env when no BYO venv/conda env is present. */
|
|
16
|
+
managedWorkspaceVenv?: boolean;
|
|
17
|
+
/** Packages to seed into the managed workspace venv when provisioning it. */
|
|
18
|
+
seedPackages?: readonly string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
11
21
|
const DEFAULT_ENV_ALLOWLIST = new Set([
|
|
12
22
|
"PATH",
|
|
13
23
|
"HOME",
|
|
@@ -104,15 +114,22 @@ function resolvePathKey(env: Record<string, string | undefined>): string {
|
|
|
104
114
|
return match ?? "PATH";
|
|
105
115
|
}
|
|
106
116
|
|
|
107
|
-
function
|
|
117
|
+
function resolveGlobalManagedPythonEnv(): string {
|
|
108
118
|
return getPythonEnvDir();
|
|
109
119
|
}
|
|
110
120
|
|
|
111
|
-
function
|
|
112
|
-
const venvPath = resolveManagedPythonEnv();
|
|
121
|
+
function resolvePythonCandidateInVenv(venvPath: string): { venvPath: string; pythonPath: string; binDir: string } {
|
|
113
122
|
const binDir = process.platform === "win32" ? path.join(venvPath, "Scripts") : path.join(venvPath, "bin");
|
|
114
123
|
const pythonPath = path.join(binDir, process.platform === "win32" ? "python.exe" : "python");
|
|
115
|
-
return { venvPath, pythonPath };
|
|
124
|
+
return { venvPath, pythonPath, binDir };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function resolveManagedPythonCandidate(): { venvPath: string; pythonPath: string; binDir: string } {
|
|
128
|
+
return resolvePythonCandidateInVenv(resolveGlobalManagedPythonEnv());
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function resolveWorkspaceManagedPythonCandidate(cwd: string): { venvPath: string; pythonPath: string; binDir: string } {
|
|
132
|
+
return resolvePythonCandidateInVenv(path.join(cwd, ".gjc", "python-env"));
|
|
116
133
|
}
|
|
117
134
|
|
|
118
135
|
export interface PythonRuntime {
|
|
@@ -124,6 +141,23 @@ export interface PythonRuntime {
|
|
|
124
141
|
venvPath?: string;
|
|
125
142
|
}
|
|
126
143
|
|
|
144
|
+
function runtimeFromVenv(
|
|
145
|
+
venvPath: string,
|
|
146
|
+
pythonPath: string,
|
|
147
|
+
binDir: string,
|
|
148
|
+
env: Record<string, string | undefined>,
|
|
149
|
+
): PythonRuntime {
|
|
150
|
+
env.VIRTUAL_ENV = venvPath;
|
|
151
|
+
const pathKey = resolvePathKey(env);
|
|
152
|
+
const currentPath = env[pathKey];
|
|
153
|
+
env[pathKey] = currentPath ? `${binDir}${path.delimiter}${currentPath}` : binDir;
|
|
154
|
+
return {
|
|
155
|
+
pythonPath,
|
|
156
|
+
env,
|
|
157
|
+
venvPath,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
127
161
|
/**
|
|
128
162
|
* Filter environment variables to a safe allowlist for Python subprocesses.
|
|
129
163
|
* Removes sensitive API keys and limits to known-safe variables.
|
|
@@ -161,42 +195,110 @@ export function resolveVenvPath(cwd: string): string | undefined {
|
|
|
161
195
|
return undefined;
|
|
162
196
|
}
|
|
163
197
|
|
|
198
|
+
async function runRuntimeCommand(
|
|
199
|
+
cmd: string[],
|
|
200
|
+
cwd: string,
|
|
201
|
+
env: Record<string, string | undefined>,
|
|
202
|
+
description: string,
|
|
203
|
+
): Promise<void> {
|
|
204
|
+
const spawnEnv: Record<string, string> = {};
|
|
205
|
+
for (const [key, value] of Object.entries(env)) {
|
|
206
|
+
if (typeof value === "string") spawnEnv[key] = value;
|
|
207
|
+
}
|
|
208
|
+
const proc = Bun.spawn(cmd, {
|
|
209
|
+
cwd,
|
|
210
|
+
env: spawnEnv,
|
|
211
|
+
stdout: "pipe",
|
|
212
|
+
stderr: "pipe",
|
|
213
|
+
windowsHide: true,
|
|
214
|
+
});
|
|
215
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
216
|
+
new Response(proc.stdout).text(),
|
|
217
|
+
new Response(proc.stderr).text(),
|
|
218
|
+
proc.exited,
|
|
219
|
+
]);
|
|
220
|
+
if (exitCode !== 0) {
|
|
221
|
+
const output = [stdout.trim(), stderr.trim()].filter(Boolean).join("\n");
|
|
222
|
+
throw new Error(`${description} failed with exit code ${exitCode}${output ? `: ${output}` : ""}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function ensureWorkspaceManagedVenv(
|
|
227
|
+
cwd: string,
|
|
228
|
+
baseEnv: Record<string, string | undefined>,
|
|
229
|
+
seedPackages: readonly string[],
|
|
230
|
+
): Promise<void> {
|
|
231
|
+
const managed = resolveWorkspaceManagedPythonCandidate(cwd);
|
|
232
|
+
if (!fs.existsSync(managed.pythonPath)) {
|
|
233
|
+
const basePython = $which("python3") ?? $which("python");
|
|
234
|
+
if (!basePython) throw new Error("Python executable not found on PATH");
|
|
235
|
+
await fs.promises.mkdir(path.dirname(managed.venvPath), { recursive: true });
|
|
236
|
+
await runRuntimeCommand(
|
|
237
|
+
[basePython, "-m", "venv", managed.venvPath],
|
|
238
|
+
cwd,
|
|
239
|
+
baseEnv,
|
|
240
|
+
"Managed Python venv creation",
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
if (seedPackages.length === 0) return;
|
|
244
|
+
const markerPath = path.join(managed.venvPath, ".gjc-seeded.json");
|
|
245
|
+
let seeded = false;
|
|
246
|
+
try {
|
|
247
|
+
const marker = JSON.parse(await fs.promises.readFile(markerPath, "utf8")) as { packages?: unknown };
|
|
248
|
+
const packages = Array.isArray(marker.packages) ? marker.packages : [];
|
|
249
|
+
seeded = seedPackages.every(pkg => packages.includes(pkg));
|
|
250
|
+
} catch {
|
|
251
|
+
seeded = false;
|
|
252
|
+
}
|
|
253
|
+
if (seeded) return;
|
|
254
|
+
const runtimeEnv = runtimeFromVenv(managed.venvPath, managed.pythonPath, managed.binDir, { ...baseEnv }).env;
|
|
255
|
+
await runRuntimeCommand(
|
|
256
|
+
[managed.pythonPath, "-m", "pip", "install", "--upgrade", "pip"],
|
|
257
|
+
cwd,
|
|
258
|
+
runtimeEnv,
|
|
259
|
+
"Managed Python pip bootstrap",
|
|
260
|
+
);
|
|
261
|
+
await runRuntimeCommand(
|
|
262
|
+
[managed.pythonPath, "-m", "pip", "install", ...seedPackages],
|
|
263
|
+
cwd,
|
|
264
|
+
runtimeEnv,
|
|
265
|
+
"Managed Python package seed",
|
|
266
|
+
);
|
|
267
|
+
await fs.promises.writeFile(
|
|
268
|
+
markerPath,
|
|
269
|
+
`${JSON.stringify({ packages: seedPackages, seededAt: new Date().toISOString() }, null, 2)}\n`,
|
|
270
|
+
"utf8",
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
164
274
|
/**
|
|
165
275
|
* Resolve Python runtime including executable path, environment, and venv detection.
|
|
166
276
|
*/
|
|
167
|
-
export function resolvePythonRuntime(
|
|
277
|
+
export function resolvePythonRuntime(
|
|
278
|
+
cwd: string,
|
|
279
|
+
baseEnv: Record<string, string | undefined>,
|
|
280
|
+
options: PythonRuntimeOptions = {},
|
|
281
|
+
): PythonRuntime {
|
|
168
282
|
const env = { ...baseEnv };
|
|
169
283
|
const venvPath = env.VIRTUAL_ENV ?? resolveVenvPath(cwd);
|
|
170
284
|
|
|
171
285
|
if (venvPath) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (fs.existsSync(pythonCandidate)) {
|
|
176
|
-
const pathKey = resolvePathKey(env);
|
|
177
|
-
const currentPath = env[pathKey];
|
|
178
|
-
env[pathKey] = currentPath ? `${binDir}${path.delimiter}${currentPath}` : binDir;
|
|
179
|
-
return {
|
|
180
|
-
pythonPath: pythonCandidate,
|
|
181
|
-
env,
|
|
182
|
-
venvPath,
|
|
183
|
-
};
|
|
286
|
+
const candidate = resolvePythonCandidateInVenv(venvPath);
|
|
287
|
+
if (fs.existsSync(candidate.pythonPath)) {
|
|
288
|
+
return runtimeFromVenv(candidate.venvPath, candidate.pythonPath, candidate.binDir, env);
|
|
184
289
|
}
|
|
185
290
|
}
|
|
186
291
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
env,
|
|
198
|
-
venvPath: managed.venvPath,
|
|
199
|
-
};
|
|
292
|
+
if (options.managedWorkspaceVenv) {
|
|
293
|
+
const workspaceManaged = resolveWorkspaceManagedPythonCandidate(cwd);
|
|
294
|
+
if (fs.existsSync(workspaceManaged.pythonPath)) {
|
|
295
|
+
return runtimeFromVenv(workspaceManaged.venvPath, workspaceManaged.pythonPath, workspaceManaged.binDir, env);
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
const managed = resolveManagedPythonCandidate();
|
|
299
|
+
if (fs.existsSync(managed.pythonPath)) {
|
|
300
|
+
return runtimeFromVenv(managed.venvPath, managed.pythonPath, managed.binDir, env);
|
|
301
|
+
}
|
|
200
302
|
}
|
|
201
303
|
|
|
202
304
|
const pythonPath = $which("python") ?? $which("python3");
|
|
@@ -208,3 +310,22 @@ export function resolvePythonRuntime(cwd: string, baseEnv: Record<string, string
|
|
|
208
310
|
env,
|
|
209
311
|
};
|
|
210
312
|
}
|
|
313
|
+
|
|
314
|
+
export async function ensurePythonRuntime(
|
|
315
|
+
cwd: string,
|
|
316
|
+
baseEnv: Record<string, string | undefined>,
|
|
317
|
+
options: PythonRuntimeOptions = {},
|
|
318
|
+
): Promise<PythonRuntime> {
|
|
319
|
+
if (options.managedWorkspaceVenv) {
|
|
320
|
+
const env = { ...baseEnv };
|
|
321
|
+
const venvPath = env.VIRTUAL_ENV ?? resolveVenvPath(cwd);
|
|
322
|
+
if (venvPath) {
|
|
323
|
+
const candidate = resolvePythonCandidateInVenv(venvPath);
|
|
324
|
+
if (fs.existsSync(candidate.pythonPath)) {
|
|
325
|
+
return runtimeFromVenv(candidate.venvPath, candidate.pythonPath, candidate.binDir, env);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
await ensureWorkspaceManagedVenv(cwd, baseEnv, options.seedPackages ?? RLM_MANAGED_PYTHON_PACKAGES);
|
|
329
|
+
}
|
|
330
|
+
return resolvePythonRuntime(cwd, baseEnv, options);
|
|
331
|
+
}
|
|
@@ -34,6 +34,10 @@ export interface BashExecutorOptions {
|
|
|
34
34
|
artifactId?: string;
|
|
35
35
|
/** Execute without retaining a native Shell in the persistent session registry. */
|
|
36
36
|
oneShot?: boolean;
|
|
37
|
+
/** Ignore user-configured shell command prefixes. Used by constrained read-only shells. */
|
|
38
|
+
ignoreShellPrefix?: boolean;
|
|
39
|
+
/** Skip sourced shell snapshots. Used by constrained read-only shells. */
|
|
40
|
+
disableShellSnapshot?: boolean;
|
|
37
41
|
/**
|
|
38
42
|
* Invoked when the native minimizer rewrote the command's output, giving
|
|
39
43
|
* the caller a chance to persist the lossless original capture (typically
|
|
@@ -119,15 +123,17 @@ export function buildMinimizerOptions(group: ShellMinimizerSettings): MinimizerO
|
|
|
119
123
|
export async function executeBash(command: string, options?: BashExecutorOptions): Promise<BashResult> {
|
|
120
124
|
const settings = await Settings.init();
|
|
121
125
|
const { shell, env: shellEnv, prefix } = settings.getShellConfig();
|
|
122
|
-
const
|
|
126
|
+
const configuredPrefix = options?.ignoreShellPrefix ? undefined : prefix;
|
|
127
|
+
const snapshotPath =
|
|
128
|
+
!options?.disableShellSnapshot && shell.includes("bash") ? await getOrCreateSnapshot(shell, shellEnv) : null;
|
|
123
129
|
|
|
124
130
|
const minimizer = buildMinimizerOptions(settings.getGroup("shellMinimizer"));
|
|
125
131
|
|
|
126
132
|
const commandCwd = await resolveShellCwd(options?.cwd);
|
|
127
133
|
const commandEnv = options?.env ? { ...NON_INTERACTIVE_ENV, ...options.env } : NON_INTERACTIVE_ENV;
|
|
128
134
|
|
|
129
|
-
// Apply command prefix if configured
|
|
130
|
-
const prefixedCommand =
|
|
135
|
+
// Apply command prefix if configured and allowed for this execution.
|
|
136
|
+
const prefixedCommand = configuredPrefix ? `${configuredPrefix} ${command}` : command;
|
|
131
137
|
const finalCommand = prefixedCommand;
|
|
132
138
|
|
|
133
139
|
// Create output sink for truncation and artifact handling
|
|
@@ -160,7 +166,7 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
160
166
|
}
|
|
161
167
|
|
|
162
168
|
const usePersistentShell = options?.oneShot !== true;
|
|
163
|
-
const sessionKey = buildSessionKey(shell,
|
|
169
|
+
const sessionKey = buildSessionKey(shell, configuredPrefix, snapshotPath, shellEnv, options?.sessionKey, minimizer);
|
|
164
170
|
const persistentSessionBroken = usePersistentShell && brokenShellSessions.has(sessionKey);
|
|
165
171
|
|
|
166
172
|
let shellSession = persistentSessionBroken || !usePersistentShell ? undefined : shellSessions.get(sessionKey);
|
|
@@ -180,6 +180,8 @@ export interface CustomTool<TParams extends TSchema = TSchema, TDetails = any> {
|
|
|
180
180
|
label: string;
|
|
181
181
|
/** If true, tool is strictly typed and validated against the parameters schema before execution */
|
|
182
182
|
strict?: boolean;
|
|
183
|
+
/** Tool scheduling mode; exclusive tools do not run in parallel with other tools in a model turn. */
|
|
184
|
+
concurrency?: "shared" | "exclusive";
|
|
183
185
|
/** Description for LLM */
|
|
184
186
|
description: string;
|
|
185
187
|
/** Parameter schema (Zod or TypeBox; TypeBox is auto-lifted to Zod at registration). */
|
|
@@ -15,6 +15,7 @@ export class CustomToolAdapter<TParams extends TSchema = TSchema, TDetails = any
|
|
|
15
15
|
declare description: string;
|
|
16
16
|
declare parameters: TParams;
|
|
17
17
|
readonly strict: boolean | undefined;
|
|
18
|
+
readonly concurrency: "shared" | "exclusive" | undefined;
|
|
18
19
|
|
|
19
20
|
constructor(
|
|
20
21
|
private tool: CustomTool<TParams, TDetails>,
|
|
@@ -22,6 +23,7 @@ export class CustomToolAdapter<TParams extends TSchema = TSchema, TDetails = any
|
|
|
22
23
|
) {
|
|
23
24
|
applyToolProxy(tool, this);
|
|
24
25
|
this.strict = tool.strict;
|
|
26
|
+
this.concurrency = tool.concurrency;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
execute(
|
|
@@ -383,6 +383,8 @@ export interface ToolDefinition<TParams extends TSchema = TSchema, TDetails = un
|
|
|
383
383
|
description: string;
|
|
384
384
|
/** Parameter schema (Zod, or TypeBox for legacy/extension compat). */
|
|
385
385
|
parameters: TParams;
|
|
386
|
+
/** Tool scheduling mode; exclusive tools do not run in parallel with other tools in a model turn. */
|
|
387
|
+
concurrency?: "shared" | "exclusive";
|
|
386
388
|
/** If true, tool is excluded unless explicitly listed in --tools or agent's tools field */
|
|
387
389
|
hidden?: boolean;
|
|
388
390
|
/** If true, tool is registered but not auto-included in the initial active set.
|
|
@@ -17,6 +17,7 @@ export class RegisteredToolAdapter implements AgentTool<any, any, any> {
|
|
|
17
17
|
declare parameters: any;
|
|
18
18
|
declare label: string;
|
|
19
19
|
declare strict: boolean;
|
|
20
|
+
declare concurrency: "shared" | "exclusive" | undefined;
|
|
20
21
|
|
|
21
22
|
renderCall?: (args: any, options: any, theme: any) => any;
|
|
22
23
|
renderResult?: (result: any, options: any, theme: any, args?: any) => any;
|