@gajae-code/coding-agent 0.16.3 → 0.16.6
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 +113 -0
- package/dist/types/capability/types.d.ts +4 -0
- package/dist/types/cli/args.d.ts +2 -2
- package/dist/types/cli/customize-doctor.d.ts +22 -0
- package/dist/types/cli/root-flags.d.ts +4 -2
- package/dist/types/cli.d.ts +1 -1
- package/dist/types/commands/launch.d.ts +1 -1
- package/dist/types/config/autorouting-tier-map.d.ts +3 -0
- package/dist/types/config/file-lock.d.ts +31 -3
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/model-resolver.d.ts +9 -0
- package/dist/types/config/settings-schema.d.ts +29 -1
- package/dist/types/coordinator-mcp/question-state.d.ts +7 -0
- package/dist/types/coordinator-mcp/server.d.ts +7 -0
- package/dist/types/discovery/claude-plugins.d.ts +3 -1
- package/dist/types/discovery/helpers.d.ts +35 -0
- package/dist/types/exec/bash-executor.d.ts +17 -1
- package/dist/types/exec/bash-shell-guardian.d.ts +15 -0
- package/dist/types/exec/bash-shell-runtime-entry.d.ts +2 -0
- package/dist/types/exec/bash-shell-signals.d.ts +2 -0
- package/dist/types/exec/bash-shell-supervisor-entry.d.ts +1 -0
- package/dist/types/exec/bash-shell-supervisor.d.ts +10 -0
- package/dist/types/exec/bash-shell-worker-entry.d.ts +2 -0
- package/dist/types/exec/bash-shell-worker-protocol.d.ts +63 -0
- package/dist/types/exec/bash-shell-worker.d.ts +1 -0
- package/dist/types/exec/isolated-shell.d.ts +19 -0
- package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +9 -0
- package/dist/types/extensibility/extensions/function-hooks.d.ts +169 -0
- package/dist/types/extensibility/extensions/index.d.ts +1 -0
- package/dist/types/extensibility/extensions/runner.d.ts +46 -6
- package/dist/types/extensibility/extensions/types.d.ts +16 -0
- package/dist/types/extensibility/extensions/wrapper.d.ts +3 -1
- package/dist/types/extensibility/gjc-plugins/constrained-hooks.d.ts +35 -2
- package/dist/types/extensibility/gjc-plugins/mcp-policy.d.ts +16 -0
- package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -0
- package/dist/types/extensibility/gjc-plugins/types.d.ts +25 -0
- package/dist/types/extensibility/hooks/types.d.ts +4 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +5 -2
- package/dist/types/extensibility/shared-events.d.ts +1 -1
- package/dist/types/extensibility/skill-management.d.ts +9 -4
- package/dist/types/extensibility/skills.d.ts +2 -0
- package/dist/types/gjc-runtime/empty-delete-gc.d.ts +4 -1
- package/dist/types/gjc-runtime/linux-proc.d.ts +2 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +14 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +41 -3
- package/dist/types/gjc-runtime/ultragoal-succession.d.ts +251 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -2
- package/dist/types/modes/components/bash-execution.d.ts +1 -0
- package/dist/types/modes/components/execution-shared.d.ts +1 -0
- package/dist/types/modes/components/settings-selector.d.ts +6 -1
- package/dist/types/modes/components/status-line/command.d.ts +35 -0
- package/dist/types/modes/components/status-line/index.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +5 -0
- package/dist/types/modes/components/tool-status-header.d.ts +14 -0
- package/dist/types/modes/controllers/event-controller.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -0
- package/dist/types/modes/types.d.ts +7 -0
- package/dist/types/runtime-mcp/client.d.ts +9 -1
- package/dist/types/runtime-mcp/manager.d.ts +7 -1
- package/dist/types/runtime-mcp/pool.d.ts +22 -3
- package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
- package/dist/types/runtime-mcp/types.d.ts +22 -0
- package/dist/types/sdk/broker/ensure.d.ts +32 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +0 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
- package/dist/types/sdk/broker/startup-failure.d.ts +4 -1
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -0
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +12 -0
- package/dist/types/sdk/bus/chat-effect-journal.d.ts +17 -2
- package/dist/types/sdk/bus/conversation-store.d.ts +11 -0
- package/dist/types/sdk/bus/lifecycle-commands.d.ts +8 -8
- package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/slack-live-provider.d.ts +6 -0
- package/dist/types/sdk/bus/slack-provider.d.ts +12 -0
- package/dist/types/sdk/bus/topic-registry.d.ts +8 -0
- package/dist/types/sdk/client/client.d.ts +6 -0
- package/dist/types/sdk/host/host.d.ts +3 -1
- package/dist/types/sdk/host/session-runtime.d.ts +26 -1
- package/dist/types/sdk/lifecycle/service.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +10 -2
- package/dist/types/sdk/session-api-key.d.ts +26 -0
- package/dist/types/sdk/session.d.ts +5 -1
- package/dist/types/sdk/transport/serve-cli.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +11 -0
- package/dist/types/session/fallback-chain-controller.d.ts +15 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
- package/dist/types/session/messages.d.ts +1 -0
- package/dist/types/session/session-manager.d.ts +3 -1
- package/dist/types/setup/paseo/announce.d.ts +112 -0
- package/dist/types/setup/paseo/setup-deps.d.ts +2 -1
- package/dist/types/skill-state/canonical-skills.d.ts +4 -0
- package/dist/types/system-prompt.d.ts +10 -0
- package/dist/types/task/executor.d.ts +5 -1
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/fetch.d.ts +7 -0
- package/dist/types/tools/github-cache.d.ts +2 -1
- package/dist/types/tools/index.d.ts +10 -0
- package/dist/types/tools/path-utils.d.ts +2 -0
- package/dist/types/tools/search.d.ts +6 -0
- package/package.json +9 -7
- package/scripts/capture-status-line-custom-editor-showcase.ts +151 -0
- package/scripts/generate-sdk-operation-inventory.ts +11 -0
- package/scripts/verify-status-line-custom-editor-showcase.ts +309 -0
- package/src/capability/index.ts +14 -2
- package/src/capability/types.ts +4 -0
- package/src/cli/args.ts +20 -9
- package/src/cli/customize-doctor.ts +155 -12
- package/src/cli/root-flags.ts +5 -3
- package/src/cli.ts +22 -0
- package/src/commands/sdk.ts +74 -31
- package/src/config/autorouting-tier-map.ts +1 -0
- package/src/config/file-lock-gc.ts +40 -2
- package/src/config/file-lock.ts +379 -31
- package/src/config/model-preset-registry.ts +120 -19
- package/src/config/model-profiles.ts +40 -0
- package/src/config/model-registry.ts +73 -23
- package/src/config/model-resolver.ts +48 -0
- package/src/config/settings-schema.ts +33 -1
- package/src/config/settings.ts +1 -0
- package/src/coordinator-mcp/question-state.ts +33 -2
- package/src/coordinator-mcp/server.ts +477 -101
- package/src/crash/sanitize.ts +1 -1
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +11 -11
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +13 -2
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +22 -0
- package/src/discovery/builtin.ts +52 -55
- package/src/discovery/claude-plugins.ts +12 -4
- package/src/discovery/helpers.ts +307 -34
- package/src/exec/bash-executor.ts +330 -50
- package/src/exec/bash-shell-guardian.ts +388 -0
- package/src/exec/bash-shell-runtime-entry.ts +5 -0
- package/src/exec/bash-shell-signals.ts +29 -0
- package/src/exec/bash-shell-supervisor-entry.ts +3 -0
- package/src/exec/bash-shell-supervisor.ts +359 -0
- package/src/exec/bash-shell-worker-entry.ts +5 -0
- package/src/exec/bash-shell-worker-protocol.ts +36 -0
- package/src/exec/bash-shell-worker.ts +116 -0
- package/src/exec/isolated-shell.ts +393 -0
- package/src/extensibility/extensions/function-hooks-internal.ts +73 -0
- package/src/extensibility/extensions/function-hooks.ts +959 -0
- package/src/extensibility/extensions/index.ts +22 -0
- package/src/extensibility/extensions/loader.ts +53 -2
- package/src/extensibility/extensions/runner.ts +895 -264
- package/src/extensibility/extensions/types.ts +42 -0
- package/src/extensibility/extensions/wrapper.ts +56 -15
- package/src/extensibility/gjc-plugins/compiler.ts +228 -60
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +321 -23
- package/src/extensibility/gjc-plugins/installer.ts +13 -12
- package/src/extensibility/gjc-plugins/mcp-policy.ts +81 -52
- package/src/extensibility/gjc-plugins/paths.ts +13 -0
- package/src/extensibility/gjc-plugins/registry.ts +113 -1
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +758 -16
- package/src/extensibility/gjc-plugins/runtime-quarantine.ts +20 -2
- package/src/extensibility/gjc-plugins/schema.ts +121 -4
- package/src/extensibility/gjc-plugins/types.ts +25 -0
- package/src/extensibility/hooks/loader.ts +133 -16
- package/src/extensibility/hooks/types.ts +28 -0
- package/src/extensibility/runtime-skill-discovery.ts +149 -190
- package/src/extensibility/shared-events.ts +1 -1
- package/src/extensibility/skill-management.ts +200 -52
- package/src/extensibility/skills.ts +73 -25
- package/src/gjc-runtime/empty-delete-gc.ts +94 -4
- package/src/gjc-runtime/gc-runtime.ts +29 -0
- package/src/gjc-runtime/linux-proc.ts +14 -0
- package/src/gjc-runtime/repository-binding.ts +1 -1
- package/src/gjc-runtime/session-state-lock.ts +143 -10
- package/src/gjc-runtime/session-state-sidecar.ts +27 -11
- package/src/gjc-runtime/state-writer.ts +30 -29
- package/src/gjc-runtime/ultragoal-runtime.ts +189 -47
- package/src/gjc-runtime/ultragoal-succession.ts +1667 -0
- package/src/gjc-runtime/workflow-manifest.generated.json +52 -1
- package/src/gjc-runtime/workflow-manifest.ts +16 -0
- package/src/harness-control-plane/session-lease.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +12 -12
- package/src/lsp/config.ts +10 -3
- package/src/lsp/index.ts +1 -1
- package/src/lsp/path-trust.ts +69 -14
- package/src/main.ts +78 -35
- package/src/modes/DESIGN.md +54 -0
- package/src/modes/acp/acp-agent.ts +16 -3
- package/src/modes/components/assistant-message.ts +22 -9
- package/src/modes/components/bash-execution.ts +4 -1
- package/src/modes/components/execution-shared.ts +4 -1
- package/src/modes/components/model-selector.ts +166 -33
- package/src/modes/components/session-observer-overlay.ts +1 -1
- package/src/modes/components/settings-selector.ts +1075 -235
- package/src/modes/components/status-line/command.ts +190 -0
- package/src/modes/components/status-line/index.ts +1 -0
- package/src/modes/components/status-line/segments.ts +32 -5
- package/src/modes/components/status-line/types.ts +5 -0
- package/src/modes/components/tool-status-header.ts +396 -22
- package/src/modes/controllers/command-controller.ts +3 -0
- package/src/modes/controllers/event-controller.ts +151 -7
- package/src/modes/controllers/extension-ui-controller.ts +1 -0
- package/src/modes/controllers/input-controller.ts +6 -3
- package/src/modes/controllers/selector-controller.ts +21 -0
- package/src/modes/interactive-mode.ts +53 -6
- package/src/modes/print-mode.ts +5 -1
- package/src/modes/prompt-action-autocomplete.ts +54 -27
- package/src/modes/runtime-init.ts +2 -0
- package/src/modes/types.ts +17 -3
- package/src/modes/utils/ui-helpers.ts +2 -0
- package/src/prompts/system/system-prompt.md +8 -0
- package/src/prompts/tools/search.md +2 -0
- package/src/prompts/tools/subagent.md +1 -0
- package/src/runtime-mcp/client.ts +60 -17
- package/src/runtime-mcp/manager.ts +189 -14
- package/src/runtime-mcp/pool.test.ts +218 -0
- package/src/runtime-mcp/pool.ts +224 -23
- package/src/runtime-mcp/transports/stdio.ts +158 -24
- package/src/runtime-mcp/types.ts +24 -0
- package/src/sdk/broker/broker.ts +124 -32
- package/src/sdk/broker/ensure.ts +248 -41
- package/src/sdk/broker/lifecycle-ledger.ts +7 -17
- package/src/sdk/broker/lifecycle.ts +56 -6
- package/src/sdk/broker/startup-failure.ts +15 -6
- package/src/sdk/bus/chat-daemon-control.ts +3 -1
- package/src/sdk/bus/chat-daemon-runtime.ts +72 -6
- package/src/sdk/bus/chat-effect-journal.ts +132 -7
- package/src/sdk/bus/conversation-store.ts +29 -0
- package/src/sdk/bus/lifecycle-commands.ts +17 -11
- package/src/sdk/bus/slack-conversation.ts +1 -0
- package/src/sdk/bus/slack-daemon.ts +478 -49
- package/src/sdk/bus/slack-live-provider.ts +12 -0
- package/src/sdk/bus/slack-provider.ts +5 -0
- package/src/sdk/bus/telegram-daemon.ts +16 -2
- package/src/sdk/bus/topic-registry.ts +19 -0
- package/src/sdk/cli/master-cli.ts +1 -1
- package/src/sdk/cli/session-cli.ts +11 -8
- package/src/sdk/client/client.ts +66 -17
- package/src/sdk/host/host.ts +29 -9
- package/src/sdk/host/query/revision-store.ts +19 -13
- package/src/sdk/host/reverse-leases.ts +7 -4
- package/src/sdk/host/session-runtime.test.ts +52 -10
- package/src/sdk/host/session-runtime.ts +383 -44
- package/src/sdk/lifecycle/service.ts +4 -0
- package/src/sdk/protocol/operation-inventory.generated.json +77 -0
- package/src/sdk/router/session-router.ts +89 -38
- package/src/sdk/session-api-key.ts +94 -0
- package/src/sdk/session.ts +228 -55
- package/src/sdk/transport/relay.ts +35 -13
- package/src/sdk/transport/serve-cli.ts +3 -3
- package/src/session/agent-session.ts +240 -90
- package/src/session/contribution-prep.ts +214 -21
- package/src/session/fallback-chain-controller.ts +38 -0
- package/src/session/internal/managed-session-storage.ts +19 -5
- package/src/session/messages.ts +5 -0
- package/src/session/session-manager.ts +29 -5
- package/src/session-import/redact.ts +5 -1
- package/src/setup/paseo/announce.ts +456 -0
- package/src/setup/paseo/setup-deps.ts +15 -3
- package/src/skill-state/canonical-skills.ts +12 -0
- package/src/skills/skills.test.ts +41 -21
- package/src/slash-commands/builtin-registry.ts +1 -0
- package/src/system-prompt.ts +29 -3
- package/src/task/executor.ts +7 -1
- package/src/task/index.ts +21 -4
- package/src/tools/bash.ts +63 -19
- package/src/tools/cron.ts +1 -1
- package/src/tools/fetch.ts +29 -1
- package/src/tools/gh.ts +26 -3
- package/src/tools/github-cache.ts +68 -16
- package/src/tools/index.ts +10 -0
- package/src/tools/job.ts +21 -4
- package/src/tools/monitor.ts +8 -2
- package/src/tools/path-utils.ts +7 -0
- package/src/tools/read.ts +14 -2
- package/src/tools/render-utils.ts +1 -3
- package/src/tools/resource-gc.ts +28 -8
- package/src/tools/search.ts +116 -58
- package/src/tools/skill-discovery.ts +5 -0
- package/src/tools/skill.ts +37 -2
- package/src/tools/subagent.ts +6 -3
- package/src/tools/tool-catalog.generated.ts +10 -3
- package/src/tools/ultragoal-ask-guard.ts +2 -2
- package/src/web/insane/bridge.ts +113 -23
- package/src/web/search/providers/perplexity.ts +102 -41
package/src/tools/index.ts
CHANGED
|
@@ -203,6 +203,8 @@ export interface ToolSession {
|
|
|
203
203
|
* Runtime discovery only; never threaded into capability loading.
|
|
204
204
|
*/
|
|
205
205
|
home?: string;
|
|
206
|
+
/** Resolver-owned classification for the session's selected agent directory. */
|
|
207
|
+
profileAuthority?: "default" | "custom";
|
|
206
208
|
skills?: Skill[];
|
|
207
209
|
/** Currently executing skill prompt, when this tool session is inside one. */
|
|
208
210
|
getActiveSkillState?: () => Pick<SkillActiveEntry, "skill" | "session_id"> | undefined;
|
|
@@ -249,6 +251,14 @@ export interface ToolSession {
|
|
|
249
251
|
waitForUserSteering?: (signal: AbortSignal) => Promise<void>;
|
|
250
252
|
/** Get session ID */
|
|
251
253
|
getSessionId?: () => string | null;
|
|
254
|
+
/**
|
|
255
|
+
* Opaque async-job ownership/lookup endpoint key. This is not a workflow or
|
|
256
|
+
* path identity: workflow/session consumers must use getSessionId(). Only
|
|
257
|
+
* AsyncJobManager ownership and endpoint-first lookup may use this accessor
|
|
258
|
+
* (subagent-inherited managers are registered under the parent's endpoint;
|
|
259
|
+
* review thread P1).
|
|
260
|
+
*/
|
|
261
|
+
getAsyncEndpointId?: () => string | null;
|
|
252
262
|
/** Get credential-selection session identity. */
|
|
253
263
|
getCredentialSessionId?: () => string | null;
|
|
254
264
|
/** Scope-held MCP facade for mcp:// resolution. */
|
package/src/tools/job.ts
CHANGED
|
@@ -105,7 +105,9 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
|
|
|
105
105
|
): Promise<AgentToolResult<JobToolDetails>> {
|
|
106
106
|
const manager =
|
|
107
107
|
this.session.getAsyncJobManager?.() ??
|
|
108
|
-
AsyncJobManager.forEndpoint(
|
|
108
|
+
AsyncJobManager.forEndpoint(
|
|
109
|
+
this.session.getAsyncEndpointId?.() ?? this.session.getSessionId?.() ?? undefined,
|
|
110
|
+
) ??
|
|
109
111
|
AsyncJobManager.instance();
|
|
110
112
|
if (!manager) {
|
|
111
113
|
return {
|
|
@@ -254,10 +256,21 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
|
|
|
254
256
|
},
|
|
255
257
|
});
|
|
256
258
|
};
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
+
const { promise: progressError, reject: rejectProgress } = Promise.withResolvers<never>();
|
|
260
|
+
racePromises.push(progressError);
|
|
261
|
+
let progressTimer: Timer | undefined;
|
|
259
262
|
|
|
260
263
|
try {
|
|
264
|
+
progressTimer = onUpdate
|
|
265
|
+
? setInterval(() => {
|
|
266
|
+
try {
|
|
267
|
+
emitProgress();
|
|
268
|
+
} catch (error) {
|
|
269
|
+
rejectProgress(error);
|
|
270
|
+
}
|
|
271
|
+
}, PROGRESS_INTERVAL_MS)
|
|
272
|
+
: undefined;
|
|
273
|
+
emitProgress();
|
|
261
274
|
if (signal) {
|
|
262
275
|
const { promise: abortPromise, resolve: abortResolve } = Promise.withResolvers<void>();
|
|
263
276
|
const onAbort = () => abortResolve();
|
|
@@ -396,7 +409,11 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
|
|
|
396
409
|
const generation = manager.getJob?.(job.id)?.generation;
|
|
397
410
|
if (!generation) continue;
|
|
398
411
|
// The endpoint disambiguates concurrent sessions' same job ids (review P1).
|
|
399
|
-
const registration = lookupOwnedRegistration(
|
|
412
|
+
const registration = lookupOwnedRegistration(
|
|
413
|
+
job.id,
|
|
414
|
+
generation,
|
|
415
|
+
this.session.getAsyncEndpointId?.() ?? this.session.getSessionId?.() ?? "local",
|
|
416
|
+
);
|
|
400
417
|
if (registration) unregisterOwnedRegistration(registration);
|
|
401
418
|
}
|
|
402
419
|
|
package/src/tools/monitor.ts
CHANGED
|
@@ -119,7 +119,9 @@ export class MonitorTool implements AgentTool<typeof monitorSchema, MonitorToolD
|
|
|
119
119
|
// different concurrent top-level session (review thread P1).
|
|
120
120
|
const manager =
|
|
121
121
|
this.session.getAsyncJobManager?.() ??
|
|
122
|
-
AsyncJobManager.forEndpoint(
|
|
122
|
+
AsyncJobManager.forEndpoint(
|
|
123
|
+
this.session.getAsyncEndpointId?.() ?? this.session.getSessionId?.() ?? undefined,
|
|
124
|
+
) ??
|
|
123
125
|
AsyncJobManager.instance();
|
|
124
126
|
if (!manager) {
|
|
125
127
|
throw new ToolError("Async execution is disabled; the monitor tool is unavailable in this session.");
|
|
@@ -197,7 +199,11 @@ export class MonitorTool implements AgentTool<typeof monitorSchema, MonitorToolD
|
|
|
197
199
|
// endpoint-less fallback scan could attach a foreign session's
|
|
198
200
|
// registration (review thread P2).
|
|
199
201
|
const registration = generation
|
|
200
|
-
? lookupOwnedRegistration(
|
|
202
|
+
? lookupOwnedRegistration(
|
|
203
|
+
jobId,
|
|
204
|
+
generation,
|
|
205
|
+
this.session.getAsyncEndpointId?.() ?? this.session.getSessionId?.() ?? "local",
|
|
206
|
+
)
|
|
201
207
|
: undefined;
|
|
202
208
|
const ownedCompletion = registration
|
|
203
209
|
? {
|
package/src/tools/path-utils.ts
CHANGED
|
@@ -696,6 +696,8 @@ export interface ToolScopeOptions {
|
|
|
696
696
|
surfaceExactFilePaths?: boolean;
|
|
697
697
|
/** Extra hint appended to "Path not found" when stat fails and the user supplied multiple paths. */
|
|
698
698
|
multipathStatHint?: string;
|
|
699
|
+
/** Cancels internal-URL resolution and prevents further scope work. */
|
|
700
|
+
signal?: AbortSignal;
|
|
699
701
|
}
|
|
700
702
|
|
|
701
703
|
export interface ToolScopeResolution {
|
|
@@ -719,6 +721,7 @@ export interface ToolScopeResolution {
|
|
|
719
721
|
*/
|
|
720
722
|
export async function resolveToolSearchScope(opts: ToolScopeOptions): Promise<ToolScopeResolution> {
|
|
721
723
|
const { rawPaths: inputs, cwd, internalUrlAction, getArtifactsDir, getAuthorizedArtifactsDirs } = opts;
|
|
724
|
+
opts.signal?.throwIfAborted();
|
|
722
725
|
const rawPaths = inputs.map(normalizePathLikeInput);
|
|
723
726
|
if (rawPaths.some(rawPath => rawPath.length === 0)) {
|
|
724
727
|
throw new ToolError("`paths` must contain non-empty paths or globs");
|
|
@@ -727,6 +730,7 @@ export async function resolveToolSearchScope(opts: ToolScopeOptions): Promise<To
|
|
|
727
730
|
const resolvedPathInputs: string[] = [];
|
|
728
731
|
const immutableSourcePaths = new Set<string>();
|
|
729
732
|
for (const rawPath of rawPaths) {
|
|
733
|
+
opts.signal?.throwIfAborted();
|
|
730
734
|
if (!internalRouter.canHandle(rawPath)) {
|
|
731
735
|
resolvedPathInputs.push(rawPath);
|
|
732
736
|
continue;
|
|
@@ -739,7 +743,9 @@ export async function resolveToolSearchScope(opts: ToolScopeOptions): Promise<To
|
|
|
739
743
|
getArtifactsDir,
|
|
740
744
|
getAuthorizedArtifactsDirs,
|
|
741
745
|
mcpManager: opts.mcpManager,
|
|
746
|
+
signal: opts.signal,
|
|
742
747
|
});
|
|
748
|
+
opts.signal?.throwIfAborted();
|
|
743
749
|
if (!resource.sourcePath) {
|
|
744
750
|
throw new ToolError(`Cannot ${internalUrlAction} internal URL without a backing file: ${rawPath}`);
|
|
745
751
|
}
|
|
@@ -753,6 +759,7 @@ export async function resolveToolSearchScope(opts: ToolScopeOptions): Promise<To
|
|
|
753
759
|
let effectivePaths = resolvedPathInputs;
|
|
754
760
|
if (resolvedPathInputs.length > 1) {
|
|
755
761
|
const partition = await partitionExistingPaths(resolvedPathInputs, cwd, parseSearchPath);
|
|
762
|
+
opts.signal?.throwIfAborted();
|
|
756
763
|
if (partition.valid.length === 0) {
|
|
757
764
|
throw new ToolError(`Path not found: ${partition.missing.join(", ")}`);
|
|
758
765
|
}
|
package/src/tools/read.ts
CHANGED
|
@@ -818,10 +818,22 @@ export async function streamLinesFromFile(
|
|
|
818
818
|
fitBytes += separatorBytes + currentLineLength;
|
|
819
819
|
} else {
|
|
820
820
|
transitionToSplit();
|
|
821
|
-
pushSplit(
|
|
821
|
+
pushSplit(
|
|
822
|
+
lineIndex,
|
|
823
|
+
lineText,
|
|
824
|
+
currentLineLength,
|
|
825
|
+
currentLineDropped,
|
|
826
|
+
currentLineDropped || currentLineLength > ringBudgetBytes ? capturePartialPreview() : undefined,
|
|
827
|
+
);
|
|
822
828
|
}
|
|
823
829
|
} else {
|
|
824
|
-
pushSplit(
|
|
830
|
+
pushSplit(
|
|
831
|
+
lineIndex,
|
|
832
|
+
lineText,
|
|
833
|
+
currentLineLength,
|
|
834
|
+
currentLineDropped,
|
|
835
|
+
currentLineDropped || currentLineLength > ringBudgetBytes ? capturePartialPreview() : undefined,
|
|
836
|
+
);
|
|
825
837
|
}
|
|
826
838
|
}
|
|
827
839
|
|
|
@@ -308,6 +308,7 @@ export function formatDiagnostics(
|
|
|
308
308
|
// Count total diagnostics for "... X more" calculation
|
|
309
309
|
const totalParsedDiags = files.reduce((sum, [, diags]) => sum + diags.length, 0);
|
|
310
310
|
const totalDiags = totalParsedDiags + unparsed.length;
|
|
311
|
+
const willShowAllRemaining = totalDiags <= maxDiags;
|
|
311
312
|
|
|
312
313
|
// Helper to check if this is the very last item in the tree
|
|
313
314
|
const isTreeEnd = (fileIdx: number, diagIdx: number | null, unparsedIdx: number | null): boolean => {
|
|
@@ -329,9 +330,6 @@ export function formatDiagnostics(
|
|
|
329
330
|
for (let fi = 0; fi < files.length && diagsShown < maxDiags; fi++) {
|
|
330
331
|
const [filePath, diagnostics] = files[fi];
|
|
331
332
|
// File is "last" only if no more files AND no unparsed AND we'll show all diags AND no "... X more"
|
|
332
|
-
const remainingDiagsInFile = diagnostics.length;
|
|
333
|
-
const remainingDiagsAfter = files.slice(fi + 1).reduce((sum, [, d]) => sum + d.length, 0) + unparsed.length;
|
|
334
|
-
const willShowAllRemaining = diagsShown + remainingDiagsInFile + remainingDiagsAfter <= maxDiags;
|
|
335
333
|
const isLastFileNode = fi === files.length - 1 && unparsed.length === 0 && willShowAllRemaining;
|
|
336
334
|
const fileBranch = isLastFileNode ? theme.tree.last : theme.tree.branch;
|
|
337
335
|
|
package/src/tools/resource-gc.ts
CHANGED
|
@@ -106,7 +106,10 @@ const defaultDeps: ResourceGcDeps = {
|
|
|
106
106
|
};
|
|
107
107
|
|
|
108
108
|
// ── Controller state (process-global; tabs/browsers are module-global too) ──────────────────
|
|
109
|
-
const activeSessions = new Map<
|
|
109
|
+
const activeSessions = new Map<
|
|
110
|
+
string,
|
|
111
|
+
{ settings: Settings; cwd: () => string; memoryGuardGeneration: symbol; memoryGuardEnabled: boolean }
|
|
112
|
+
>();
|
|
110
113
|
const scheduler = new MemoryGuardHost({
|
|
111
114
|
run: async () => {
|
|
112
115
|
await sweepOnce(deps);
|
|
@@ -141,12 +144,26 @@ function resolveSessionSweepIntervalMs(settings: Settings): number {
|
|
|
141
144
|
export function registerResourceGcSession(reg: ResourceGcRegistration): () => void {
|
|
142
145
|
const registeredCwd = reg.cwd;
|
|
143
146
|
const cwd = typeof registeredCwd === "function" ? registeredCwd : () => registeredCwd ?? process.cwd();
|
|
144
|
-
|
|
147
|
+
const registration = {
|
|
148
|
+
settings: reg.settings,
|
|
149
|
+
cwd,
|
|
150
|
+
memoryGuardGeneration: Symbol(),
|
|
151
|
+
memoryGuardEnabled: resolveMemoryGuardPolicy(reg.settings).enabled,
|
|
152
|
+
};
|
|
153
|
+
activeSessions.set(reg.sessionId, registration);
|
|
145
154
|
const unregisterSchedule = scheduler.register({
|
|
146
155
|
ownerId: reg.sessionId,
|
|
147
156
|
intervalMs: resolveSessionSweepIntervalMs(reg.settings),
|
|
148
157
|
});
|
|
149
158
|
const unregisterSettings = reg.settings.onChanged(path => {
|
|
159
|
+
if (path === "memoryGuard.enabled") {
|
|
160
|
+
const enabled = resolveMemoryGuardPolicy(reg.settings).enabled;
|
|
161
|
+
if (enabled !== registration.memoryGuardEnabled) {
|
|
162
|
+
// Fence samples already in flight, even if the guard is re-enabled before they settle.
|
|
163
|
+
registration.memoryGuardGeneration = Symbol();
|
|
164
|
+
registration.memoryGuardEnabled = enabled;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
150
167
|
if (
|
|
151
168
|
path === "memoryGuard.enabled" ||
|
|
152
169
|
path === "memoryGuard.checkIntervalMs" ||
|
|
@@ -498,8 +515,8 @@ function sweepMemoryPressureGuard(d: ResourceGcDeps): Promise<void> | undefined
|
|
|
498
515
|
}
|
|
499
516
|
async function sweepEnabledMemoryPressureGuard(d: ResourceGcDeps): Promise<void> {
|
|
500
517
|
const now = d.monotonicNow();
|
|
501
|
-
const dueSessions: Array<{ sessionId: string; policy: MemoryGuardPolicy; cwd: string }> = [];
|
|
502
|
-
for (const [sessionId, { settings, cwd: resolveCwd }] of activeSessions) {
|
|
518
|
+
const dueSessions: Array<{ sessionId: string; policy: MemoryGuardPolicy; cwd: string; generation: symbol }> = [];
|
|
519
|
+
for (const [sessionId, { settings, cwd: resolveCwd, memoryGuardGeneration }] of activeSessions) {
|
|
503
520
|
const policy = resolveMemoryGuardPolicy(settings);
|
|
504
521
|
const cwd = resolveCwd();
|
|
505
522
|
if (!policy.enabled) {
|
|
@@ -511,14 +528,15 @@ async function sweepEnabledMemoryPressureGuard(d: ResourceGcDeps): Promise<void>
|
|
|
511
528
|
const lastEvaluated = memoryGuardLastEvaluatedAt.get(sessionId);
|
|
512
529
|
if (lastEvaluated !== undefined && now - lastEvaluated < policy.checkIntervalMs) continue;
|
|
513
530
|
memoryGuardLastEvaluatedAt.set(sessionId, now);
|
|
514
|
-
dueSessions.push({ sessionId, policy, cwd });
|
|
531
|
+
dueSessions.push({ sessionId, policy, cwd, generation: memoryGuardGeneration });
|
|
515
532
|
}
|
|
516
533
|
if (dueSessions.length === 0) return;
|
|
517
534
|
|
|
518
535
|
const snapshot = await d.memorySnapshot();
|
|
519
536
|
let gcRequested = false;
|
|
520
|
-
const gcTelemetry: Array<{ sessionId: string } & Record<string, unknown>> = [];
|
|
521
|
-
for (const { sessionId, policy } of dueSessions) {
|
|
537
|
+
const gcTelemetry: Array<{ sessionId: string; generation: symbol } & Record<string, unknown>> = [];
|
|
538
|
+
for (const { sessionId, policy, generation } of dueSessions) {
|
|
539
|
+
if (activeSessions.get(sessionId)?.memoryGuardGeneration !== generation) continue;
|
|
522
540
|
const pressure = __selectMemoryPressureDomainForTest(snapshot, policy.policyLimitBytes);
|
|
523
541
|
const limit = resolveEffectiveMemoryLimit({
|
|
524
542
|
hardCapBytes: pressure.hardCapBytes,
|
|
@@ -548,6 +566,7 @@ async function sweepEnabledMemoryPressureGuard(d: ResourceGcDeps): Promise<void>
|
|
|
548
566
|
gcRequested = true;
|
|
549
567
|
gcTelemetry.push({
|
|
550
568
|
sessionId,
|
|
569
|
+
generation,
|
|
551
570
|
parentBytes: pressure.parentBytes,
|
|
552
571
|
totalUsageBytes: pressure.totalUsageBytes,
|
|
553
572
|
effectiveLimitBytes: limit.effectiveBytes,
|
|
@@ -587,7 +606,8 @@ async function sweepEnabledMemoryPressureGuard(d: ResourceGcDeps): Promise<void>
|
|
|
587
606
|
if (gcRequested) {
|
|
588
607
|
try {
|
|
589
608
|
d.runGc();
|
|
590
|
-
for (const { sessionId, ...telemetry } of gcTelemetry) {
|
|
609
|
+
for (const { sessionId, generation, ...telemetry } of gcTelemetry) {
|
|
610
|
+
if (activeSessions.get(sessionId)?.memoryGuardGeneration !== generation) continue;
|
|
591
611
|
memoryGuardGcActive.add(sessionId);
|
|
592
612
|
d.logWarn("Memory guard: GC threshold reached", { sessionId, ...telemetry });
|
|
593
613
|
}
|
package/src/tools/search.ts
CHANGED
|
@@ -5,7 +5,7 @@ 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
|
|
8
|
+
import { prompt } from "@gajae-code/utils";
|
|
9
9
|
import * as z from "zod/v4";
|
|
10
10
|
import { getFileReadCache } from "../edit/file-read-cache";
|
|
11
11
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
PREVIEW_LIMITS,
|
|
36
36
|
splitGroupsByBlankLine,
|
|
37
37
|
} from "./render-utils";
|
|
38
|
-
import { ToolError } from "./tool-errors";
|
|
38
|
+
import { ToolAbortError, ToolError } from "./tool-errors";
|
|
39
39
|
import { toolResult } from "./tool-result";
|
|
40
40
|
|
|
41
41
|
let searchNativesLoad:
|
|
@@ -71,6 +71,7 @@ const searchSchema = z
|
|
|
71
71
|
.number()
|
|
72
72
|
.optional()
|
|
73
73
|
.describe("files to skip before collecting results — use to paginate when the prior call hit the file limit"),
|
|
74
|
+
timeout: z.number().min(0.5).max(60).default(5).describe("timeout in seconds (0.5–60)").optional(),
|
|
74
75
|
})
|
|
75
76
|
.strict();
|
|
76
77
|
|
|
@@ -90,6 +91,9 @@ export const SINGLE_FILE_MATCHES = 200;
|
|
|
90
91
|
* (DEFAULT_FILE_LIMIT files × MULTI_FILE_PER_FILE_MATCHES matches) plus
|
|
91
92
|
* pagination headroom so the caller can see total file count. */
|
|
92
93
|
const INTERNAL_TOTAL_CAP = 2000;
|
|
94
|
+
const DEFAULT_SEARCH_TIMEOUT_MS = 5000;
|
|
95
|
+
const MIN_SEARCH_TIMEOUT_MS = 500;
|
|
96
|
+
const MAX_SEARCH_TIMEOUT_MS = 60_000;
|
|
93
97
|
|
|
94
98
|
/**
|
|
95
99
|
* Detect a `,` that is not inside a `{…}` brace expansion. Used to catch
|
|
@@ -130,6 +134,7 @@ function containsTopLevelComma(entry: string): boolean {
|
|
|
130
134
|
async function resolveArchiveSearchPaths(
|
|
131
135
|
paths: string[],
|
|
132
136
|
cwd: string,
|
|
137
|
+
signal?: AbortSignal,
|
|
133
138
|
): Promise<{
|
|
134
139
|
resolvedPaths: string[];
|
|
135
140
|
displayMap: Map<string, string>;
|
|
@@ -143,64 +148,71 @@ async function resolveArchiveSearchPaths(
|
|
|
143
148
|
const unreadable: string[] = [];
|
|
144
149
|
let tempDir: string | undefined;
|
|
145
150
|
const archiveCache = new Map<string, ArchiveReader>();
|
|
151
|
+
const cleanup = async () => {
|
|
152
|
+
const directory = tempDir;
|
|
153
|
+
tempDir = undefined;
|
|
154
|
+
if (directory) await rm(directory, { recursive: true, force: true }).catch(() => {});
|
|
155
|
+
};
|
|
146
156
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
157
|
+
try {
|
|
158
|
+
for (let idx = 0; idx < paths.length; idx++) {
|
|
159
|
+
signal?.throwIfAborted();
|
|
160
|
+
const entry = paths[idx];
|
|
161
|
+
const candidates = parseArchivePathCandidates(entry);
|
|
162
|
+
// Longest archive prefix first; we want the one whose member portion is non-empty.
|
|
163
|
+
const member = candidates.find(c => c.subPath !== "" && c.archivePath !== entry);
|
|
164
|
+
if (!member) continue;
|
|
165
|
+
|
|
166
|
+
const archiveAbs = resolveReadPath(member.archivePath, cwd);
|
|
167
|
+
let archive = archiveCache.get(archiveAbs);
|
|
168
|
+
if (!archive) {
|
|
169
|
+
try {
|
|
170
|
+
archive = await openArchive(archiveAbs);
|
|
171
|
+
} catch (err) {
|
|
172
|
+
unreadable.push(`${entry} (cannot open archive: ${(err as Error).message})`);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
archiveCache.set(archiveAbs, archive);
|
|
176
|
+
}
|
|
153
177
|
|
|
154
|
-
|
|
155
|
-
let archive = archiveCache.get(archiveAbs);
|
|
156
|
-
if (!archive) {
|
|
178
|
+
let extracted: ExtractedArchiveFile;
|
|
157
179
|
try {
|
|
158
|
-
|
|
180
|
+
extracted = await archive.readFile(member.subPath);
|
|
159
181
|
} catch (err) {
|
|
160
|
-
unreadable.push(`${entry} (
|
|
182
|
+
unreadable.push(`${entry} (${(err as Error).message})`);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
signal?.throwIfAborted();
|
|
186
|
+
// UTF-8 only — binary members would just produce noise through ripgrep.
|
|
187
|
+
if (extracted.bytes.some(byte => byte === 0)) {
|
|
188
|
+
unreadable.push(`${entry} (binary archive entry)`);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
let text: string;
|
|
192
|
+
try {
|
|
193
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(extracted.bytes);
|
|
194
|
+
} catch {
|
|
195
|
+
unreadable.push(`${entry} (non-UTF-8 archive entry)`);
|
|
161
196
|
continue;
|
|
162
197
|
}
|
|
163
|
-
archiveCache.set(archiveAbs, archive);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
let extracted: ExtractedArchiveFile;
|
|
167
|
-
try {
|
|
168
|
-
extracted = await archive.readFile(member.subPath);
|
|
169
|
-
} catch (err) {
|
|
170
|
-
unreadable.push(`${entry} (${(err as Error).message})`);
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
// UTF-8 only — binary members would just produce noise through ripgrep.
|
|
174
|
-
if (extracted.bytes.some(byte => byte === 0)) {
|
|
175
|
-
unreadable.push(`${entry} (binary archive entry)`);
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
let text: string;
|
|
179
|
-
try {
|
|
180
|
-
text = new TextDecoder("utf-8", { fatal: true }).decode(extracted.bytes);
|
|
181
|
-
} catch {
|
|
182
|
-
unreadable.push(`${entry} (non-UTF-8 archive entry)`);
|
|
183
|
-
continue;
|
|
184
|
-
}
|
|
185
198
|
|
|
186
|
-
|
|
187
|
-
|
|
199
|
+
if (!tempDir) {
|
|
200
|
+
tempDir = await mkdtemp(path.join(tmpdir(), "gjc-search-archive-"));
|
|
201
|
+
}
|
|
202
|
+
// Per-entry filename keeps the scratch path unique even when two selectors
|
|
203
|
+
// resolve to members with the same basename.
|
|
204
|
+
const safeBase = path.basename(member.subPath).replace(/[^\w.-]+/g, "_") || "entry";
|
|
205
|
+
const tempPath = path.join(tempDir, `${idx}-${safeBase}`);
|
|
206
|
+
await writeFile(tempPath, text);
|
|
207
|
+
resolvedPaths[idx] = tempPath;
|
|
208
|
+
displayMap.set(tempPath, entry);
|
|
209
|
+
displaySet.add(entry);
|
|
188
210
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const tempPath = path.join(tempDir, `${idx}-${safeBase}`);
|
|
193
|
-
await writeFile(tempPath, text);
|
|
194
|
-
resolvedPaths[idx] = tempPath;
|
|
195
|
-
displayMap.set(tempPath, entry);
|
|
196
|
-
displaySet.add(entry);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
await cleanup();
|
|
213
|
+
throw error;
|
|
197
214
|
}
|
|
198
215
|
|
|
199
|
-
const cleanup = async () => {
|
|
200
|
-
if (tempDir) {
|
|
201
|
-
await rm(tempDir, { recursive: true, force: true }).catch(() => {});
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
216
|
return { resolvedPaths, displayMap, displaySet, unreadable, cleanup };
|
|
205
217
|
}
|
|
206
218
|
|
|
@@ -235,6 +247,8 @@ export interface SearchToolOptions {
|
|
|
235
247
|
context?: number;
|
|
236
248
|
/** Per-call total match cap used by Cursor's native Pi grep frame. */
|
|
237
249
|
totalMatchLimit?: number;
|
|
250
|
+
/** Test or remote override for the native grep implementation. */
|
|
251
|
+
grep?: typeof grepFn;
|
|
238
252
|
}
|
|
239
253
|
|
|
240
254
|
type SearchParams = z.infer<typeof searchSchema>;
|
|
@@ -266,12 +280,23 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
|
|
|
266
280
|
_onUpdate?: AgentToolUpdateCallback<SearchToolDetails>,
|
|
267
281
|
_toolContext?: AgentToolContext,
|
|
268
282
|
): Promise<AgentToolResult<SearchToolDetails>> {
|
|
269
|
-
const { pattern, i, gitignore, skip } = params;
|
|
283
|
+
const { pattern, i, gitignore, skip, timeout } = params;
|
|
270
284
|
// Recurring real-world failure mode: models omit `paths` for repo-wide
|
|
271
285
|
// searches. Default to the working directory instead of failing the call.
|
|
272
286
|
const paths = params.paths ?? ["."];
|
|
287
|
+
const requestedTimeoutMs = timeout != null ? Math.round(timeout * 1000) : DEFAULT_SEARCH_TIMEOUT_MS;
|
|
288
|
+
const timeoutMs = Math.min(MAX_SEARCH_TIMEOUT_MS, Math.max(MIN_SEARCH_TIMEOUT_MS, requestedTimeoutMs));
|
|
289
|
+
const deadlineAt = Date.now() + timeoutMs;
|
|
290
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
291
|
+
const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
292
|
+
const throwIfSearchCancelled = (): void => {
|
|
293
|
+
signal?.throwIfAborted();
|
|
294
|
+
if (Date.now() >= deadlineAt) throw new Error("Aborted: Timeout");
|
|
295
|
+
combinedSignal.throwIfAborted();
|
|
296
|
+
};
|
|
273
297
|
|
|
274
|
-
|
|
298
|
+
try {
|
|
299
|
+
throwIfSearchCancelled();
|
|
275
300
|
const normalizedPattern = pattern.trim();
|
|
276
301
|
if (!normalizedPattern) {
|
|
277
302
|
throw new ToolError("Pattern must not be empty");
|
|
@@ -292,8 +317,10 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
|
|
|
292
317
|
displaySet: archiveDisplaySet,
|
|
293
318
|
unreadable: archiveUnreadable,
|
|
294
319
|
cleanup: cleanupArchiveScratch,
|
|
295
|
-
} = await resolveArchiveSearchPaths(paths, this.session.cwd);
|
|
320
|
+
} = await resolveArchiveSearchPaths(paths, this.session.cwd, combinedSignal);
|
|
321
|
+
let completedResult: AgentToolResult<SearchToolDetails> | undefined;
|
|
296
322
|
try {
|
|
323
|
+
throwIfSearchCancelled();
|
|
297
324
|
if (archiveUnreadable.length > 0 && resolvedPaths.length === archiveUnreadable.length) {
|
|
298
325
|
// All inputs were archive selectors we couldn't materialize; surface the
|
|
299
326
|
// reason instead of a downstream "path not found" from the scope resolver.
|
|
@@ -324,7 +351,9 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
|
|
|
324
351
|
trackImmutableSources: true,
|
|
325
352
|
surfaceExactFilePaths: true,
|
|
326
353
|
multipathStatHint: " (`paths` entries must each exist relative to cwd)",
|
|
354
|
+
signal: combinedSignal,
|
|
327
355
|
});
|
|
356
|
+
throwIfSearchCancelled();
|
|
328
357
|
const { searchPath, isDirectory, multiTargets, exactFilePaths, missingPaths, immutableSourcePaths } = scope;
|
|
329
358
|
// When the only input was an archive selector, surface that selector instead
|
|
330
359
|
// of the temp scratch path the resolver substituted in.
|
|
@@ -345,7 +374,8 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
|
|
|
345
374
|
const baseDisplayMode = resolveFileDisplayMode(this.session);
|
|
346
375
|
const immutableDisplayMode = resolveFileDisplayMode(this.session, { immutable: true });
|
|
347
376
|
|
|
348
|
-
const { GrepOutputMode, grep } = await searchNatives();
|
|
377
|
+
const { GrepOutputMode, grep: nativeGrep } = await searchNatives();
|
|
378
|
+
const grep = this.options.grep ?? nativeGrep;
|
|
349
379
|
const effectiveOutputMode = GrepOutputMode.Content;
|
|
350
380
|
// Multi-scope = more than one file may match. We fetch up to
|
|
351
381
|
// INTERNAL_TOTAL_CAP matches from native grep, then in JS group by
|
|
@@ -363,7 +393,10 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
|
|
|
363
393
|
let totalMatches = 0;
|
|
364
394
|
let filesSearched = 0;
|
|
365
395
|
const targets = exactFilePaths
|
|
366
|
-
? exactFilePaths.map(filePath => ({
|
|
396
|
+
? exactFilePaths.map(filePath => ({
|
|
397
|
+
basePath: filePath,
|
|
398
|
+
glob: undefined as string | undefined,
|
|
399
|
+
}))
|
|
367
400
|
: (multiTargets ?? []);
|
|
368
401
|
for (const target of targets) {
|
|
369
402
|
const targetResult = await grep(
|
|
@@ -381,9 +414,12 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
|
|
|
381
414
|
contextAfter: normalizedContextAfter,
|
|
382
415
|
maxColumns: DEFAULT_MAX_COLUMN,
|
|
383
416
|
mode: effectiveOutputMode,
|
|
417
|
+
signal: combinedSignal,
|
|
418
|
+
timeoutMs,
|
|
384
419
|
},
|
|
385
420
|
undefined,
|
|
386
421
|
);
|
|
422
|
+
throwIfSearchCancelled();
|
|
387
423
|
limitReached = limitReached || Boolean(targetResult.limitReached);
|
|
388
424
|
totalMatches += targetResult.totalMatches;
|
|
389
425
|
filesSearched += targetResult.filesSearched;
|
|
@@ -416,9 +452,12 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
|
|
|
416
452
|
contextAfter: normalizedContextAfter,
|
|
417
453
|
maxColumns: DEFAULT_MAX_COLUMN,
|
|
418
454
|
mode: effectiveOutputMode,
|
|
455
|
+
signal: combinedSignal,
|
|
456
|
+
timeoutMs,
|
|
419
457
|
},
|
|
420
458
|
undefined,
|
|
421
459
|
);
|
|
460
|
+
throwIfSearchCancelled();
|
|
422
461
|
}
|
|
423
462
|
} catch (err) {
|
|
424
463
|
if (err instanceof Error && err.message.startsWith("regex parse error")) {
|
|
@@ -518,7 +557,10 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
|
|
|
518
557
|
missingPaths: missingPaths.length > 0 ? missingPaths : undefined,
|
|
519
558
|
};
|
|
520
559
|
const text = warningNote ? `No matches found\n${warningNote}` : "No matches found";
|
|
521
|
-
|
|
560
|
+
const noMatchResult = toolResult(details).text(text).done();
|
|
561
|
+
await cleanupArchiveScratch();
|
|
562
|
+
throwIfSearchCancelled();
|
|
563
|
+
return noMatchResult;
|
|
522
564
|
}
|
|
523
565
|
const outputLines: string[] = [];
|
|
524
566
|
let linesTruncated = false;
|
|
@@ -644,11 +686,25 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
|
|
|
644
686
|
if (truncation.truncated) {
|
|
645
687
|
resultBuilder.truncation(truncation, { direction: "head" });
|
|
646
688
|
}
|
|
647
|
-
|
|
689
|
+
throwIfSearchCancelled();
|
|
690
|
+
completedResult = resultBuilder.done();
|
|
648
691
|
} finally {
|
|
649
692
|
await cleanupArchiveScratch();
|
|
650
693
|
}
|
|
651
|
-
|
|
694
|
+
throwIfSearchCancelled();
|
|
695
|
+
if (!completedResult) throw new Error("Search result assembly did not complete");
|
|
696
|
+
return completedResult;
|
|
697
|
+
} catch (error) {
|
|
698
|
+
const nativeTimedOut = error instanceof Error && error.message === "Aborted: Timeout";
|
|
699
|
+
if ((timeoutSignal.aborted || nativeTimedOut) && !signal?.aborted) {
|
|
700
|
+
const seconds = timeoutMs % 1000 === 0 ? `${timeoutMs / 1000}` : (timeoutMs / 1000).toFixed(1);
|
|
701
|
+
throw new ToolError(`Search timed out after ${seconds}s; increase timeout or narrow paths/pattern`);
|
|
702
|
+
}
|
|
703
|
+
if (combinedSignal.aborted || (error instanceof Error && error.name === "AbortError")) {
|
|
704
|
+
throw new ToolAbortError();
|
|
705
|
+
}
|
|
706
|
+
throw error;
|
|
707
|
+
}
|
|
652
708
|
}
|
|
653
709
|
}
|
|
654
710
|
|
|
@@ -662,6 +718,7 @@ interface SearchRenderArgs {
|
|
|
662
718
|
i?: boolean;
|
|
663
719
|
gitignore?: boolean;
|
|
664
720
|
skip?: number;
|
|
721
|
+
timeout?: number;
|
|
665
722
|
}
|
|
666
723
|
|
|
667
724
|
const COLLAPSED_TEXT_LIMIT = PREVIEW_LIMITS.COLLAPSED_LINES * 2;
|
|
@@ -674,6 +731,7 @@ export const searchToolRenderer = {
|
|
|
674
731
|
if (args.i) meta.push("case:insensitive");
|
|
675
732
|
if (args.gitignore === false) meta.push("gitignore:false");
|
|
676
733
|
if (args.skip !== undefined && args.skip > 0) meta.push(`skip:${args.skip}`);
|
|
734
|
+
if (args.timeout !== undefined && args.timeout !== 5) meta.push(`timeout:${args.timeout}s`);
|
|
677
735
|
|
|
678
736
|
const text = renderStatusLine(
|
|
679
737
|
{ icon: "pending", title: "Search", description: args.pattern || "?", meta },
|
|
@@ -76,9 +76,14 @@ export class SkillDiscoveryTool implements AgentTool<typeof skillDiscoverySchema
|
|
|
76
76
|
): Promise<AgentToolResult<SkillDiscoveryToolDetails>> {
|
|
77
77
|
return untilAborted(signal, async () => {
|
|
78
78
|
const source = input.source ?? "all";
|
|
79
|
+
const agentDir =
|
|
80
|
+
this.#session.getSessionAgentDir?.() ??
|
|
81
|
+
(this.#session.home === undefined ? this.#session.settings.getAgentDir() : undefined);
|
|
79
82
|
const result = await discoverRuntimeSkills({
|
|
80
83
|
cwd: this.#session.cwd,
|
|
81
84
|
home: this.#session.home,
|
|
85
|
+
agentDir,
|
|
86
|
+
profileAuthority: this.#session.profileAuthority,
|
|
82
87
|
query: input.query,
|
|
83
88
|
source,
|
|
84
89
|
limit: input.limit,
|