@gajae-code/coding-agent 0.18.0 → 0.18.5
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 +95 -0
- package/dist/types/cli/stats-cli.d.ts +0 -5
- package/dist/types/config/autorouting-tier-map.d.ts +29 -2
- package/dist/types/config/model-registry.d.ts +16 -1
- package/dist/types/config/settings-schema.d.ts +2 -2
- package/dist/types/exa/mcp-client.d.ts +1 -5
- package/dist/types/exec/bash-shell-guardian.d.ts +26 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +1 -0
- package/dist/types/hashline/parser.d.ts +28 -5
- package/dist/types/modes/acp/acp-agent.d.ts +6 -1
- package/dist/types/sdk/acp/adapter.d.ts +3 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +6 -1
- package/dist/types/sdk/broker/recovery-backoff.d.ts +62 -0
- package/dist/types/sdk/broker/runtime.d.ts +20 -0
- package/dist/types/sdk/broker/startup-budget.d.ts +5 -1
- package/dist/types/sdk/bus/index.d.ts +1 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/client/client.d.ts +5 -1
- package/dist/types/sdk/host/session-runtime.d.ts +7 -0
- package/dist/types/sdk/prompt-watchdog.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/task/index.d.ts +0 -1
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/descriptors.d.ts +5 -0
- package/dist/types/tools/eval-backends.d.ts +37 -0
- package/dist/types/tools/eval.d.ts +1 -6
- package/dist/types/tools/index.d.ts +1 -36
- package/dist/types/tools/session-descriptions.d.ts +22 -0
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +2 -0
- package/src/cli/doctor/managed-link.ts +4 -4
- package/src/cli/doctor/repairs.ts +9 -1
- package/src/cli/stats-cli.ts +4 -36
- package/src/cli-main.ts +4 -8
- package/src/cli.ts +4 -8
- package/src/commands/commit.ts +2 -2
- package/src/commands/sdk.ts +1 -2
- package/src/config/autorouting-tier-map.ts +20 -2
- package/src/config/model-preset-registry.ts +17 -2
- package/src/config/model-profile-activation.ts +3 -1
- package/src/config/model-profiles.ts +11 -3
- package/src/config/model-registry.ts +45 -17
- package/src/config/settings-schema.ts +8 -2
- package/src/exa/mcp-client.ts +41 -8
- package/src/exec/bash-shell-guardian.ts +214 -48
- package/src/gjc-runtime/session-state-sidecar.ts +88 -58
- package/src/hashline/execute.ts +51 -3
- package/src/hashline/parser.ts +63 -16
- package/src/internal-urls/docs-index.generated.ts +25 -25
- package/src/modes/acp/acp-agent.ts +136 -20
- package/src/modes/controllers/ssh-command-controller.ts +3 -1
- package/src/modes/interactive-mode.ts +10 -0
- package/src/modes/utils/context-usage.ts +2 -6
- package/src/prompts/tools/task.md +8 -27
- package/src/sdk/acp/adapter.ts +38 -3
- package/src/sdk/broker/doctor-restart.ts +38 -3
- package/src/sdk/broker/lifecycle.ts +408 -218
- package/src/sdk/broker/recovery-backoff.ts +135 -0
- package/src/sdk/broker/runtime.ts +83 -0
- package/src/sdk/broker/startup-budget.ts +6 -1
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/index.ts +308 -22
- package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/client/client.ts +53 -35
- package/src/sdk/host/session-runtime.ts +99 -3
- package/src/sdk/prompt-watchdog.ts +17 -4
- package/src/sdk/protocol/operation-inventory.generated.json +11 -0
- package/src/session/agent-session.ts +79 -17
- package/src/session/internal/managed-session-storage.ts +23 -0
- package/src/task/index.ts +21 -66
- package/src/tools/bash.ts +39 -0
- package/src/tools/descriptors.ts +27 -1
- package/src/tools/eval-backends.ts +95 -0
- package/src/tools/eval.ts +3 -15
- package/src/tools/index.ts +9 -88
- package/src/tools/search.ts +2 -7
- package/src/tools/session-descriptions.ts +135 -0
- package/src/tools/tool-catalog.generated.ts +1 -1
- package/dist/types/cli/grep-cli.d.ts +0 -17
- package/dist/types/cli/shell-cli.d.ts +0 -8
- package/dist/types/commands/grep.d.ts +0 -42
- package/dist/types/commands/shell.d.ts +0 -21
- package/src/cli/grep-cli.ts +0 -160
- package/src/cli/shell-cli.ts +0 -176
- package/src/commands/grep.ts +0 -48
- package/src/commands/shell.ts +0 -29
|
@@ -165,6 +165,27 @@ function isRetryableReplacementReceiptCleanupError(error: unknown): boolean {
|
|
|
165
165
|
return error instanceof Error && error.message === "managed_replace_receipt_cleanup_pending:io_error";
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
/**
|
|
169
|
+
* A receipt retirement slot is named only from the receipt and predecessor
|
|
170
|
+
* identities, so every process sharing a managed scope — the publisher retiring
|
|
171
|
+
* its own receipt and each peer reconciler that listed it — claims the same
|
|
172
|
+
* slot. Native exact-unlink reports `quarantine_collision` from its no-replace
|
|
173
|
+
* claim before any rename, so a collision that carries no detached or retained
|
|
174
|
+
* path proves only that a peer owns the retirement right now: this process
|
|
175
|
+
* changed nothing, the receipt stays where a later reconciliation finds it, and
|
|
176
|
+
* the unrelated mutation that triggered the scan must not fail.
|
|
177
|
+
*/
|
|
178
|
+
function isPeerOwnedReceiptRetirement(result: NativeExactUnlinkResult): boolean {
|
|
179
|
+
return (
|
|
180
|
+
!result.ok &&
|
|
181
|
+
result.code === "quarantine_collision" &&
|
|
182
|
+
result.detachedPath === undefined &&
|
|
183
|
+
result.retainedSuccessorPath === undefined &&
|
|
184
|
+
result.retainedPlaceholderPath === undefined &&
|
|
185
|
+
result.retainedUnknownPath === undefined
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
168
189
|
/** A same-filesystem rename updates the moved root's ctime but no other tree identity. */
|
|
169
190
|
function sameDirectoryTreeSnapshotAfterRename(
|
|
170
191
|
left: NativeDirectoryTreeSnapshot,
|
|
@@ -1622,6 +1643,7 @@ export class ManagedSessionDescendantStore {
|
|
|
1622
1643
|
sha256: placeholder.identity.sha256,
|
|
1623
1644
|
quarantineName,
|
|
1624
1645
|
});
|
|
1646
|
+
if (isPeerOwnedReceiptRetirement(removed)) return true;
|
|
1625
1647
|
if (
|
|
1626
1648
|
(!exactUnlinkCompleted(removed) && removed.code !== "not_found") ||
|
|
1627
1649
|
removed.retainedSuccessorPath !== undefined ||
|
|
@@ -1660,6 +1682,7 @@ export class ManagedSessionDescendantStore {
|
|
|
1660
1682
|
detachOnly: true,
|
|
1661
1683
|
quarantineName,
|
|
1662
1684
|
});
|
|
1685
|
+
if (isPeerOwnedReceiptRetirement(detached)) return;
|
|
1663
1686
|
if (
|
|
1664
1687
|
!detached.ok &&
|
|
1665
1688
|
detached.code === "not_found" &&
|
package/src/task/index.ts
CHANGED
|
@@ -32,11 +32,11 @@ import { resolveProfileBindings } from "../config/model-profiles";
|
|
|
32
32
|
import { resolveAgentModelPatterns } from "../config/model-resolver";
|
|
33
33
|
import type { Theme } from "../modes/theme/theme";
|
|
34
34
|
import planModeSubagentPrompt from "../prompts/system/plan-mode-subagent.md" with { type: "text" };
|
|
35
|
-
import taskDescriptionTemplate from "../prompts/tools/task.md" with { type: "text" };
|
|
36
35
|
import taskSummaryTemplate from "../prompts/tools/task-summary.md" with { type: "text" };
|
|
37
36
|
import type { ForkContextSeed } from "../session/agent-session";
|
|
38
37
|
import { splitSelectorThinkingSuffix } from "../thinking";
|
|
39
38
|
import { formatBytes, formatDuration } from "../tools/render-utils";
|
|
39
|
+
import { renderTaskDescription } from "../tools/session-descriptions";
|
|
40
40
|
import { escapeXmlAttribute } from "../utils/xml-escape";
|
|
41
41
|
import {
|
|
42
42
|
type AgentDefinition,
|
|
@@ -317,57 +317,26 @@ export {
|
|
|
317
317
|
taskSchema,
|
|
318
318
|
} from "./types";
|
|
319
319
|
|
|
320
|
-
/**
|
|
321
|
-
* Render the tool description from a cached agent list and current settings.
|
|
322
|
-
*/
|
|
323
320
|
function hasAvailableIrcTool(session: ToolSession): boolean {
|
|
324
321
|
return session.settings.get("irc.enabled") === true && session.getToolByName?.("irc") !== undefined;
|
|
325
322
|
}
|
|
326
323
|
|
|
327
|
-
function
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
)
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
if (spawningDisabled) {
|
|
343
|
-
filteredAgents = [];
|
|
344
|
-
} else if (parentSpawns !== "*") {
|
|
345
|
-
const allowed = new Set(
|
|
346
|
-
parentSpawns
|
|
347
|
-
.split(",")
|
|
348
|
-
.map(s => s.trim())
|
|
349
|
-
.filter(Boolean),
|
|
350
|
-
);
|
|
351
|
-
filteredAgents = filteredAgents.filter(a => allowed.has(a.name));
|
|
352
|
-
}
|
|
353
|
-
const { contextEnabled, customSchemaEnabled } = getTaskSimpleModeCapabilities(simpleMode);
|
|
354
|
-
const description = prompt.render(taskDescriptionTemplate, {
|
|
355
|
-
agents: filteredAgents,
|
|
356
|
-
spawningDisabled,
|
|
357
|
-
MAX_CONCURRENCY: maxConcurrency,
|
|
358
|
-
isolationEnabled,
|
|
359
|
-
asyncEnabled,
|
|
360
|
-
contextEnabled,
|
|
361
|
-
customSchemaEnabled,
|
|
362
|
-
ircEnabled,
|
|
363
|
-
defaultMode: simpleMode === "default",
|
|
364
|
-
schemaFreeMode: simpleMode === "schema-free",
|
|
365
|
-
independentMode: simpleMode === "independent",
|
|
366
|
-
autoroutingActive,
|
|
367
|
-
});
|
|
368
|
-
return description;
|
|
324
|
+
function callableAgents(agents: readonly AgentDefinition[], session: ToolSession): AgentDefinition[] {
|
|
325
|
+
const disabledAgents = new Set(session.settings.get("task.disabledAgents"));
|
|
326
|
+
const parentSpawns = session.getSessionSpawns() ?? "*";
|
|
327
|
+
const allowedSpawns =
|
|
328
|
+
parentSpawns === "*"
|
|
329
|
+
? undefined
|
|
330
|
+
: new Set(
|
|
331
|
+
parentSpawns
|
|
332
|
+
.split(",")
|
|
333
|
+
.map(agent => agent.trim())
|
|
334
|
+
.filter(Boolean),
|
|
335
|
+
);
|
|
336
|
+
return filterVisibleAgents(agents).filter(
|
|
337
|
+
agent => !disabledAgents.has(agent.name) && (allowedSpawns === undefined || allowedSpawns.has(agent.name)),
|
|
338
|
+
);
|
|
369
339
|
}
|
|
370
|
-
|
|
371
340
|
function createTaskModeError(text: string): AgentToolResult<TaskToolDetails> {
|
|
372
341
|
return {
|
|
373
342
|
content: [{ type: "text", text }],
|
|
@@ -614,22 +583,8 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
614
583
|
return renderTaskCall(args as TaskParams, options, theme);
|
|
615
584
|
}
|
|
616
585
|
|
|
617
|
-
/** Dynamic description that reflects current disabled-agent settings */
|
|
618
586
|
get description(): string {
|
|
619
|
-
|
|
620
|
-
const maxConcurrency = this.session.settings.get("task.maxConcurrency");
|
|
621
|
-
const isolationMode = this.session.settings.get("task.isolation.mode");
|
|
622
|
-
return renderDescription(
|
|
623
|
-
this.#discoveredAgents,
|
|
624
|
-
maxConcurrency,
|
|
625
|
-
isolationMode !== "none",
|
|
626
|
-
true,
|
|
627
|
-
disabledAgents,
|
|
628
|
-
this.#getTaskSimpleMode(),
|
|
629
|
-
hasAvailableIrcTool(this.session),
|
|
630
|
-
this.session.getSessionSpawns() ?? "*",
|
|
631
|
-
this.session.settings.getEffectiveAutorouting().active,
|
|
632
|
-
);
|
|
587
|
+
return renderTaskDescription(this.session);
|
|
633
588
|
}
|
|
634
589
|
readonly #sessionRepositoryBinding: RepositoryBinding;
|
|
635
590
|
#testRunSubprocess: typeof runSubprocess | undefined;
|
|
@@ -945,8 +900,8 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
945
900
|
const agent = getAgent(this.#discoveredAgents, params.agent);
|
|
946
901
|
if (!agent) {
|
|
947
902
|
const available =
|
|
948
|
-
|
|
949
|
-
.map(
|
|
903
|
+
callableAgents(this.#discoveredAgents, this.session)
|
|
904
|
+
.map(agent => agent.name)
|
|
950
905
|
.join(", ") || "none";
|
|
951
906
|
return {
|
|
952
907
|
content: [{ type: "text", text: `Unknown agent "${params.agent}". Available: ${available}` }],
|
|
@@ -1714,8 +1669,8 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1714
1669
|
const agent = getAgent(agents, agentName);
|
|
1715
1670
|
if (!agent) {
|
|
1716
1671
|
const available =
|
|
1717
|
-
|
|
1718
|
-
.map(
|
|
1672
|
+
callableAgents(agents, this.session)
|
|
1673
|
+
.map(agent => agent.name)
|
|
1719
1674
|
.join(", ") || "none";
|
|
1720
1675
|
return {
|
|
1721
1676
|
content: [
|
package/src/tools/bash.ts
CHANGED
|
@@ -10,10 +10,26 @@ import { type BashArtifactSaveResult, type BashResult, executeBash } from "../ex
|
|
|
10
10
|
|
|
11
11
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
12
12
|
import { buildGjcRuntimeSessionEnv } from "../gjc-runtime/goal-mode-request";
|
|
13
|
+
import {
|
|
14
|
+
MANAGED_OWNER_PREDECESSOR_GENERATION_ENV,
|
|
15
|
+
MANAGED_OWNER_PREDECESSOR_INCARNATION_ENV,
|
|
16
|
+
MANAGED_OWNER_PREDECESSOR_RUN_ID_ENV,
|
|
17
|
+
MANAGED_OWNER_PREDECESSOR_TOKEN_ENV,
|
|
18
|
+
MANAGED_OWNER_TRANSCRIPT_PATH_ENV,
|
|
19
|
+
} from "../gjc-runtime/managed-owner-admission";
|
|
20
|
+
import {
|
|
21
|
+
MANAGED_OWNER_CHILD_TOKEN_ENV,
|
|
22
|
+
MANAGED_OWNER_GENERATION_ENV,
|
|
23
|
+
MANAGED_OWNER_INCARNATION_ENV,
|
|
24
|
+
MANAGED_OWNER_RUN_ID_ENV,
|
|
25
|
+
MANAGED_OWNER_STATE_DIR_ENV,
|
|
26
|
+
} from "../gjc-runtime/managed-owner-supervisor";
|
|
13
27
|
import {
|
|
14
28
|
GJC_RALPLAN_ARTIFACT_ENV,
|
|
15
29
|
GJC_RESTRICTED_ROLE_AGENT_BASH_ENV,
|
|
16
30
|
} from "../gjc-runtime/restricted-role-agent-bash";
|
|
31
|
+
import { GJC_TMUX_OWNER_SERVER_KEY_ENV } from "../gjc-runtime/session-state-sidecar";
|
|
32
|
+
import { GJC_TMUX_LAUNCHED_ENV } from "../gjc-runtime/windows-powershell-command";
|
|
17
33
|
import { InternalUrlRouter } from "../internal-urls";
|
|
18
34
|
import { truncateToVisualLines } from "../modes/components/visual-truncate";
|
|
19
35
|
import { highlightCode, type Theme } from "../modes/theme/theme";
|
|
@@ -94,6 +110,27 @@ const ARTIFACT_SAVE_DIAGNOSTIC_MAX_BYTES = 256;
|
|
|
94
110
|
const BASH_ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
95
111
|
const MASTER_CAPABILITY_ENV = "GJC_MASTER_CAPABILITY";
|
|
96
112
|
const MASTER_OWNER_SESSION_ENV = "GJC_MASTER_OWNER_SESSION_ID";
|
|
113
|
+
// Managed-owner env vars that must be scrubbed from child processes.
|
|
114
|
+
// Exported for testing the env scrubbing behavior.
|
|
115
|
+
export const MANAGED_OWNER_BASH_ENV = [
|
|
116
|
+
MANAGED_OWNER_STATE_DIR_ENV,
|
|
117
|
+
MANAGED_OWNER_GENERATION_ENV,
|
|
118
|
+
MANAGED_OWNER_RUN_ID_ENV,
|
|
119
|
+
MANAGED_OWNER_INCARNATION_ENV,
|
|
120
|
+
MANAGED_OWNER_CHILD_TOKEN_ENV,
|
|
121
|
+
MANAGED_OWNER_PREDECESSOR_TOKEN_ENV,
|
|
122
|
+
MANAGED_OWNER_PREDECESSOR_GENERATION_ENV,
|
|
123
|
+
MANAGED_OWNER_PREDECESSOR_RUN_ID_ENV,
|
|
124
|
+
MANAGED_OWNER_PREDECESSOR_INCARNATION_ENV,
|
|
125
|
+
MANAGED_OWNER_TRANSCRIPT_PATH_ENV,
|
|
126
|
+
// The rest of the tmux-owner tuple (tmux-sessions.ts managedEnvironment): leaving
|
|
127
|
+
// either behind makes ownerTerminalContextFromEnvironment() report "invalid" in a
|
|
128
|
+
// nested gjc, because a server key or GJC_TMUX_LAUNCHED=1 without generation/state
|
|
129
|
+
// dir is an incomplete owner context.
|
|
130
|
+
GJC_TMUX_OWNER_SERVER_KEY_ENV,
|
|
131
|
+
GJC_TMUX_LAUNCHED_ENV,
|
|
132
|
+
] as const;
|
|
133
|
+
|
|
97
134
|
const COORDINATOR_ONLY_BASH_ENV = [
|
|
98
135
|
"GJC_COORDINATOR_SESSION_STATE_FILE",
|
|
99
136
|
"GJC_COORDINATOR_SESSION_ID",
|
|
@@ -102,6 +139,8 @@ const COORDINATOR_ONLY_BASH_ENV = [
|
|
|
102
139
|
"GJC_COORDINATOR_SESSION_READINESS_FILE",
|
|
103
140
|
"GJC_COORDINATOR_SIDECAR_SIGNATURE_REQUIRED",
|
|
104
141
|
"GJC_COORDINATOR_SIDECAR_KEY_ID",
|
|
142
|
+
// Managed-owner env family from managed-owner-supervisor.ts and managed-owner-admission.ts
|
|
143
|
+
...MANAGED_OWNER_BASH_ENV,
|
|
105
144
|
] as const;
|
|
106
145
|
const DEFAULT_AUTO_BACKGROUND_THRESHOLD_MS = 60_000;
|
|
107
146
|
const ACP_RELEASE_TIMEOUT_MS = 1_000;
|
package/src/tools/descriptors.ts
CHANGED
|
@@ -11,6 +11,12 @@ import {
|
|
|
11
11
|
validateDeferredAskArguments,
|
|
12
12
|
validateDeferredTodoArguments,
|
|
13
13
|
} from "./descriptor-validation";
|
|
14
|
+
import {
|
|
15
|
+
evalToolDescriptionForSession,
|
|
16
|
+
renderTaskDescription,
|
|
17
|
+
searchToolDescriptionForSession,
|
|
18
|
+
taskParametersForSession,
|
|
19
|
+
} from "./session-descriptions";
|
|
14
20
|
import { TOOL_CATALOG, type ToolCatalogEntry } from "./tool-catalog.generated";
|
|
15
21
|
import { ToolError } from "./tool-errors";
|
|
16
22
|
|
|
@@ -33,6 +39,11 @@ export interface ToolDescriptorMetadata {
|
|
|
33
39
|
readonly intent?: AgentTool<any, any, any>["intent"];
|
|
34
40
|
readonly parametersForSession?: (session?: ToolSession) => TSchema;
|
|
35
41
|
readonly parameters?: TSchema;
|
|
42
|
+
/**
|
|
43
|
+
* Renders the description the loaded tool will report for this session. Tools whose prompt text
|
|
44
|
+
* depends on session settings need it, or the advertised text changes on first load (#5992).
|
|
45
|
+
*/
|
|
46
|
+
readonly descriptionForSession?: (session: ToolSession) => string;
|
|
36
47
|
readonly description?: string;
|
|
37
48
|
readonly label?: string;
|
|
38
49
|
readonly customWireName?: string;
|
|
@@ -121,8 +132,10 @@ export class LazyAgentTool implements AgentTool<any, any, any> {
|
|
|
121
132
|
return this.#tool?.name ?? this.descriptor.metadata.name;
|
|
122
133
|
}
|
|
123
134
|
get description(): string {
|
|
135
|
+
const session = this.#session;
|
|
124
136
|
return (
|
|
125
137
|
this.#tool?.description ??
|
|
138
|
+
(session ? this.descriptor.metadata.descriptionForSession?.(session) : undefined) ??
|
|
126
139
|
this.descriptor.metadata.description ??
|
|
127
140
|
this.descriptor.presentation.summary ??
|
|
128
141
|
this.descriptor.metadata.summary ??
|
|
@@ -393,6 +406,7 @@ function descriptor(spec: DescriptorSpec): ToolDescriptor {
|
|
|
393
406
|
inline: catalog?.inline ?? spec.inline,
|
|
394
407
|
rawArgumentValidation: spec.rawArgumentValidation,
|
|
395
408
|
parametersForSession: spec.parametersForSession,
|
|
409
|
+
descriptionForSession: spec.descriptionForSession,
|
|
396
410
|
intent: catalog?.intent ?? spec.intent,
|
|
397
411
|
description: catalog?.description ?? spec.description,
|
|
398
412
|
parameters: spec.parameters ?? (catalog?.parameters as TSchema | undefined),
|
|
@@ -465,6 +479,15 @@ const names: Array<[name: string, label: string, summary: string | undefined, lo
|
|
|
465
479
|
["move_session", "Move Session", undefined, "essential"],
|
|
466
480
|
];
|
|
467
481
|
|
|
482
|
+
const sessionDescriptions: Readonly<Record<string, ToolDescriptorMetadata["descriptionForSession"]>> = {
|
|
483
|
+
eval: evalToolDescriptionForSession,
|
|
484
|
+
search: searchToolDescriptionForSession,
|
|
485
|
+
task: renderTaskDescription,
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
const sessionParameters: Readonly<Record<string, ToolDescriptorMetadata["parametersForSession"]>> = {
|
|
489
|
+
task: taskParametersForSession,
|
|
490
|
+
};
|
|
468
491
|
const descriptorRawArgumentValidations: Readonly<Record<string, ToolDescriptorMetadata["rawArgumentValidation"]>> = {
|
|
469
492
|
ask: validateDeferredAskArguments,
|
|
470
493
|
todo_write: validateDeferredTodoArguments,
|
|
@@ -487,7 +510,10 @@ const builtins = names
|
|
|
487
510
|
: undefined,
|
|
488
511
|
parameters: name === "ask" ? deferredAskParameters : undefined,
|
|
489
512
|
parametersForSession:
|
|
490
|
-
name === "ask"
|
|
513
|
+
name === "ask"
|
|
514
|
+
? session => selectAskParameters(session?.getDeepInterviewAskStage?.())
|
|
515
|
+
: sessionParameters[name],
|
|
516
|
+
descriptionForSession: sessionDescriptions[name],
|
|
491
517
|
rawArgumentValidation: descriptorRawArgumentValidations[name],
|
|
492
518
|
intent: deferredIntentPolicies[name],
|
|
493
519
|
loader: loaders[name],
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Eval backend allowance (`GJC_PY`, legacy `PI_PY` / `PI_JS`, then `eval.py` / `eval.js` settings).
|
|
3
|
+
* Kept out of the tools index so lightweight modules, such as the pre-load tool descriptions, can
|
|
4
|
+
* resolve it without importing the whole registry.
|
|
5
|
+
*/
|
|
6
|
+
import { $env } from "@gajae-code/utils";
|
|
7
|
+
import type { ToolSession } from ".";
|
|
8
|
+
|
|
9
|
+
export interface EvalBackendsAllowance {
|
|
10
|
+
python: boolean;
|
|
11
|
+
js: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Parse the `GJC_PY` multi-value token into per-backend booleans.
|
|
16
|
+
*
|
|
17
|
+
* Tokens (case-insensitive):
|
|
18
|
+
* - `0` / `bash` → JavaScript only (`{ py: false, js: true }`)
|
|
19
|
+
* - `1` / `py` → Python only (`{ py: true, js: false }`)
|
|
20
|
+
* - `js` → JavaScript only (`{ py: false, js: true }`)
|
|
21
|
+
* - `mix` / `both` → both backends (`{ py: true, js: true }`)
|
|
22
|
+
*
|
|
23
|
+
* Returns `null` when `GJC_PY` is unset, empty, or holds an unrecognized
|
|
24
|
+
* token, so the caller can fall back to the legacy `PI_PY` / `PI_JS` flags or
|
|
25
|
+
* per-key settings. This matches the documented contract that invalid values
|
|
26
|
+
* are ignored.
|
|
27
|
+
*/
|
|
28
|
+
export function parseGjcPy(env: Record<string, string | undefined>): { py: boolean; js: boolean } | null {
|
|
29
|
+
const raw = env.GJC_PY;
|
|
30
|
+
if (raw === undefined) return null;
|
|
31
|
+
const token = raw.trim().toLowerCase();
|
|
32
|
+
if (token === "") return null;
|
|
33
|
+
switch (token) {
|
|
34
|
+
case "0":
|
|
35
|
+
case "bash":
|
|
36
|
+
return { py: false, js: true };
|
|
37
|
+
case "1":
|
|
38
|
+
case "py":
|
|
39
|
+
return { py: true, js: false };
|
|
40
|
+
case "js":
|
|
41
|
+
return { py: false, js: true };
|
|
42
|
+
case "mix":
|
|
43
|
+
case "both":
|
|
44
|
+
return { py: true, js: true };
|
|
45
|
+
default:
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isTruthyPythonFlag(value: string | undefined): boolean {
|
|
51
|
+
return value !== undefined && ["1", "true", "yes", "on", "y"].includes(value.trim().toLowerCase());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Parse legacy `PI_PY` / `PI_JS` boolean flags. Each is a boolean flag; unset
|
|
56
|
+
* means "not specified, defer to settings". Returns `null` when neither is set
|
|
57
|
+
* so the caller can fall through to `readEvalBackendsAllowance` per key.
|
|
58
|
+
*/
|
|
59
|
+
function parseLegacyEvalEnvFlags(env: Record<string, string | undefined>): EvalBackendsAllowance | null {
|
|
60
|
+
const pyEnv = env.PI_PY;
|
|
61
|
+
const jsEnv = env.PI_JS;
|
|
62
|
+
if (pyEnv === undefined && jsEnv === undefined) return null;
|
|
63
|
+
return {
|
|
64
|
+
python: pyEnv === undefined ? true : isTruthyPythonFlag(pyEnv),
|
|
65
|
+
js: jsEnv === undefined ? true : isTruthyPythonFlag(jsEnv),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Resolve eval-backend allowance from environment only. `GJC_PY` wins when set
|
|
71
|
+
* to a recognized token; otherwise the legacy `PI_PY` / `PI_JS` flags apply.
|
|
72
|
+
* Returns `null` when no env override is set so the caller can defer to settings.
|
|
73
|
+
*/
|
|
74
|
+
export function resolveEvalBackendsFromEnv(env: Record<string, string | undefined>): EvalBackendsAllowance | null {
|
|
75
|
+
const gjc = parseGjcPy(env);
|
|
76
|
+
if (gjc) return { python: gjc.py, js: gjc.js };
|
|
77
|
+
return parseLegacyEvalEnvFlags(env);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Read per-backend allowance from settings (defaults true). */
|
|
81
|
+
export function readEvalBackendsAllowance(session: ToolSession): EvalBackendsAllowance {
|
|
82
|
+
return {
|
|
83
|
+
python: session.settings.get("eval.py") ?? true,
|
|
84
|
+
js: session.settings.get("eval.js") ?? true,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Materialize the active eval backend allowance. `GJC_PY` takes precedence
|
|
90
|
+
* over the legacy `PI_PY` / `PI_JS` env flags, which in turn override the
|
|
91
|
+
* per-key settings (defaults true). When no env override is set, settings win.
|
|
92
|
+
*/
|
|
93
|
+
export function resolveEvalBackends(session: ToolSession): EvalBackendsAllowance {
|
|
94
|
+
return resolveEvalBackendsFromEnv($env) ?? readEvalBackendsAllowance(session);
|
|
95
|
+
}
|
package/src/tools/eval.ts
CHANGED
|
@@ -2,7 +2,6 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
|
|
2
2
|
import type { ImageContent } from "@gajae-code/ai/core";
|
|
3
3
|
import type { Component } from "@gajae-code/tui";
|
|
4
4
|
import { Markdown, Text } from "@gajae-code/tui";
|
|
5
|
-
import { prompt } from "@gajae-code/utils";
|
|
6
5
|
import * as z from "zod/v4";
|
|
7
6
|
import { jsBackend, pythonBackend } from "../eval";
|
|
8
7
|
import type { ExecutorBackend } from "../eval/backend";
|
|
@@ -10,7 +9,6 @@ import type { EvalCellResult, EvalDisplayOutput, EvalLanguage, EvalStatusEvent,
|
|
|
10
9
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
11
10
|
import { truncateToVisualLines } from "../modes/components/visual-truncate";
|
|
12
11
|
import { getMarkdownTheme, type Theme } from "../modes/theme/theme";
|
|
13
|
-
import evalDescription from "../prompts/tools/eval.md" with { type: "text" };
|
|
14
12
|
import { DEFAULT_MAX_BYTES, OutputSink, type OutputSummary, TailBuffer } from "../session/streaming-output";
|
|
15
13
|
import { getTreeBranch, getTreeContinuePrefix, renderCodeCell } from "../tui";
|
|
16
14
|
import { resolveEvalBackends, type ToolSession } from ".";
|
|
@@ -21,6 +19,7 @@ import {
|
|
|
21
19
|
stripOutputNotice,
|
|
22
20
|
} from "./output-meta";
|
|
23
21
|
import { formatTitle, replaceTabs, shortenPath, truncateToWidth, wrapBrackets } from "./render-utils";
|
|
22
|
+
import { evalToolDescriptionForSession } from "./session-descriptions";
|
|
24
23
|
import { ToolAbortError, ToolError } from "./tool-errors";
|
|
25
24
|
import { toolResult } from "./tool-result";
|
|
26
25
|
import { clampTimeout } from "./tool-timeouts";
|
|
@@ -134,16 +133,7 @@ function renderJsonTree(value: unknown, theme: Theme, expanded: boolean, maxDept
|
|
|
134
133
|
return renderNode(value, "", 0, true);
|
|
135
134
|
}
|
|
136
135
|
|
|
137
|
-
export
|
|
138
|
-
py?: boolean;
|
|
139
|
-
js?: boolean;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export function getEvalToolDescription(options: EvalToolDescriptionOptions = {}): string {
|
|
143
|
-
const py = options.py ?? true;
|
|
144
|
-
const js = options.js ?? true;
|
|
145
|
-
return prompt.render(evalDescription, { py, js });
|
|
146
|
-
}
|
|
136
|
+
export { type EvalToolDescriptionOptions, getEvalToolDescription } from "./session-descriptions";
|
|
147
137
|
|
|
148
138
|
export interface EvalToolOptions {
|
|
149
139
|
proxyExecutor?: EvalProxyExecutor;
|
|
@@ -204,9 +194,7 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
|
|
|
204
194
|
readonly loadMode = "discoverable";
|
|
205
195
|
readonly label = "Eval";
|
|
206
196
|
get description(): string {
|
|
207
|
-
|
|
208
|
-
const backends = resolveEvalBackends(this.session);
|
|
209
|
-
return getEvalToolDescription({ py: backends.python, js: backends.js });
|
|
197
|
+
return evalToolDescriptionForSession(this.session);
|
|
210
198
|
}
|
|
211
199
|
readonly parameters = evalSchema;
|
|
212
200
|
readonly concurrency = "exclusive";
|
package/src/tools/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentTelemetryConfig, AgentTool } from "@gajae-code/agent-core";
|
|
2
2
|
import type { Model, ServiceTier, ToolChoice } from "@gajae-code/ai/core";
|
|
3
|
-
import {
|
|
3
|
+
import { logger } from "@gajae-code/utils";
|
|
4
4
|
import type { AsyncJobManager, FoldReason } from "../async";
|
|
5
5
|
import type { PromptTemplate } from "../config/prompt-templates";
|
|
6
6
|
import type { Settings } from "../config/settings";
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
resolveEffectiveDiscoveryMode,
|
|
38
38
|
type ToolAvailabilityContext,
|
|
39
39
|
} from "./descriptors";
|
|
40
|
+
import { resolveEvalBackends } from "./eval-backends";
|
|
40
41
|
import { wrapToolWithMetaNotice } from "./output-meta";
|
|
41
42
|
import type { TodoPhase } from "./todo-write";
|
|
42
43
|
|
|
@@ -491,93 +492,13 @@ const GOAL_MODE_TOOL_NAMES = [] as const;
|
|
|
491
492
|
|
|
492
493
|
export type ToolName = keyof typeof BUILTIN_TOOLS;
|
|
493
494
|
|
|
494
|
-
export
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
*
|
|
502
|
-
* Tokens (case-insensitive):
|
|
503
|
-
* - `0` / `bash` → JavaScript only (`{ py: false, js: true }`)
|
|
504
|
-
* - `1` / `py` → Python only (`{ py: true, js: false }`)
|
|
505
|
-
* - `js` → JavaScript only (`{ py: false, js: true }`)
|
|
506
|
-
* - `mix` / `both` → both backends (`{ py: true, js: true }`)
|
|
507
|
-
*
|
|
508
|
-
* Returns `null` when `GJC_PY` is unset, empty, or holds an unrecognized
|
|
509
|
-
* token, so the caller can fall back to the legacy `PI_PY` / `PI_JS` flags or
|
|
510
|
-
* per-key settings. This matches the documented contract that invalid values
|
|
511
|
-
* are ignored.
|
|
512
|
-
*/
|
|
513
|
-
export function parseGjcPy(env: Record<string, string | undefined>): { py: boolean; js: boolean } | null {
|
|
514
|
-
const raw = env.GJC_PY;
|
|
515
|
-
if (raw === undefined) return null;
|
|
516
|
-
const token = raw.trim().toLowerCase();
|
|
517
|
-
if (token === "") return null;
|
|
518
|
-
switch (token) {
|
|
519
|
-
case "0":
|
|
520
|
-
case "bash":
|
|
521
|
-
return { py: false, js: true };
|
|
522
|
-
case "1":
|
|
523
|
-
case "py":
|
|
524
|
-
return { py: true, js: false };
|
|
525
|
-
case "js":
|
|
526
|
-
return { py: false, js: true };
|
|
527
|
-
case "mix":
|
|
528
|
-
case "both":
|
|
529
|
-
return { py: true, js: true };
|
|
530
|
-
default:
|
|
531
|
-
return null;
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
function isTruthyPythonFlag(value: string | undefined): boolean {
|
|
536
|
-
return value !== undefined && ["1", "true", "yes", "on", "y"].includes(value.trim().toLowerCase());
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
/**
|
|
540
|
-
* Parse legacy `PI_PY` / `PI_JS` boolean flags. Each is a boolean flag; unset
|
|
541
|
-
* means "not specified, defer to settings". Returns `null` when neither is set
|
|
542
|
-
* so the caller can fall through to `readEvalBackendsAllowance` per key.
|
|
543
|
-
*/
|
|
544
|
-
function parseLegacyEvalEnvFlags(env: Record<string, string | undefined>): EvalBackendsAllowance | null {
|
|
545
|
-
const pyEnv = env.PI_PY;
|
|
546
|
-
const jsEnv = env.PI_JS;
|
|
547
|
-
if (pyEnv === undefined && jsEnv === undefined) return null;
|
|
548
|
-
return {
|
|
549
|
-
python: pyEnv === undefined ? true : isTruthyPythonFlag(pyEnv),
|
|
550
|
-
js: jsEnv === undefined ? true : isTruthyPythonFlag(jsEnv),
|
|
551
|
-
};
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
/**
|
|
555
|
-
* Resolve eval-backend allowance from environment only. `GJC_PY` wins when set
|
|
556
|
-
* to a recognized token; otherwise the legacy `PI_PY` / `PI_JS` flags apply.
|
|
557
|
-
* Returns `null` when no env override is set so the caller can defer to settings.
|
|
558
|
-
*/
|
|
559
|
-
export function resolveEvalBackendsFromEnv(env: Record<string, string | undefined>): EvalBackendsAllowance | null {
|
|
560
|
-
const gjc = parseGjcPy(env);
|
|
561
|
-
if (gjc) return { python: gjc.py, js: gjc.js };
|
|
562
|
-
return parseLegacyEvalEnvFlags(env);
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
/** Read per-backend allowance from settings (defaults true). */
|
|
566
|
-
export function readEvalBackendsAllowance(session: ToolSession): EvalBackendsAllowance {
|
|
567
|
-
return {
|
|
568
|
-
python: session.settings.get("eval.py") ?? true,
|
|
569
|
-
js: session.settings.get("eval.js") ?? true,
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
/**
|
|
574
|
-
* Materialize the active eval backend allowance. `GJC_PY` takes precedence
|
|
575
|
-
* over the legacy `PI_PY` / `PI_JS` env flags, which in turn override the
|
|
576
|
-
* per-key settings (defaults true). When no env override is set, settings win.
|
|
577
|
-
*/
|
|
578
|
-
export function resolveEvalBackends(session: ToolSession): EvalBackendsAllowance {
|
|
579
|
-
return resolveEvalBackendsFromEnv($env) ?? readEvalBackendsAllowance(session);
|
|
580
|
-
}
|
|
495
|
+
export {
|
|
496
|
+
type EvalBackendsAllowance,
|
|
497
|
+
parseGjcPy,
|
|
498
|
+
readEvalBackendsAllowance,
|
|
499
|
+
resolveEvalBackends,
|
|
500
|
+
resolveEvalBackendsFromEnv,
|
|
501
|
+
} from "./eval-backends";
|
|
581
502
|
|
|
582
503
|
/**
|
|
583
504
|
* Create tools from the descriptor registry.
|
package/src/tools/search.ts
CHANGED
|
@@ -5,12 +5,10 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
|
|
5
5
|
import type { GrepMatch, GrepResult, grep as grepFn } from "@gajae-code/natives";
|
|
6
6
|
import type { Component } from "@gajae-code/tui";
|
|
7
7
|
import { Text } from "@gajae-code/tui";
|
|
8
|
-
import { prompt } from "@gajae-code/utils";
|
|
9
8
|
import * as z from "zod/v4";
|
|
10
9
|
import { getFileReadCache } from "../edit/file-read-cache";
|
|
11
10
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
12
11
|
import type { Theme } from "../modes/theme/theme";
|
|
13
|
-
import searchDescription from "../prompts/tools/search.md" with { type: "text" };
|
|
14
12
|
import { DEFAULT_MAX_COLUMN, type TruncationResult, truncateHead } from "../session/streaming-output";
|
|
15
13
|
import { Ellipsis, fileHyperlink, renderStatusLine, renderTreeList, truncateToWidth } from "../tui";
|
|
16
14
|
import { resolveFileDisplayMode } from "../utils/file-display-mode";
|
|
@@ -35,6 +33,7 @@ import {
|
|
|
35
33
|
PREVIEW_LIMITS,
|
|
36
34
|
splitGroupsByBlankLine,
|
|
37
35
|
} from "./render-utils";
|
|
36
|
+
import { searchToolDescriptionForSession } from "./session-descriptions";
|
|
38
37
|
import { ToolAbortError, ToolError } from "./tool-errors";
|
|
39
38
|
import { toolResult } from "./tool-result";
|
|
40
39
|
|
|
@@ -259,11 +258,7 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
|
|
|
259
258
|
readonly loadMode = "discoverable";
|
|
260
259
|
readonly summary = "Search file contents using ripgrep (fast text search)";
|
|
261
260
|
get description(): string {
|
|
262
|
-
|
|
263
|
-
return prompt.render(searchDescription, {
|
|
264
|
-
IS_HL_MODE: displayMode.hashLines,
|
|
265
|
-
IS_LINE_NUMBER_MODE: !displayMode.hashLines && displayMode.lineNumbers,
|
|
266
|
-
});
|
|
261
|
+
return searchToolDescriptionForSession(this.session);
|
|
267
262
|
}
|
|
268
263
|
readonly parameters = searchSchema;
|
|
269
264
|
readonly strict = true;
|