@gajae-code/coding-agent 0.16.0 → 0.16.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 +41 -1
- package/dist/types/async/job-manager.d.ts +32 -1
- package/dist/types/browser-backend.d.ts +12 -0
- package/dist/types/cli/update-cli.d.ts +1 -0
- package/dist/types/commands/sdk.d.ts +2 -0
- package/dist/types/config/billing-path.d.ts +25 -0
- package/dist/types/config/config-schema-version.d.ts +13 -1
- package/dist/types/config/model-registry.d.ts +16 -0
- package/dist/types/config/settings-schema.d.ts +17 -7
- package/dist/types/extensibility/extensions/types.d.ts +8 -3
- package/dist/types/gjc-runtime/launch-worktree.d.ts +25 -1
- package/dist/types/gjc-runtime/session-state-lock.d.ts +22 -0
- package/dist/types/main.d.ts +1 -0
- package/dist/types/modes/acp/acp-agent.d.ts +21 -21
- package/dist/types/modes/components/model-selector.d.ts +4 -0
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/sessions-dashboard.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +16 -2
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/sdk/broker/broker.d.ts +2 -1
- package/dist/types/sdk/broker/ensure.d.ts +3 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +6 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +14 -0
- package/dist/types/sdk/broker/startup-budget.d.ts +52 -0
- package/dist/types/sdk/broker/state-version.d.ts +17 -0
- package/dist/types/sdk/bus/discord-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/index.d.ts +3 -1
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +1 -1
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +1 -1
- package/dist/types/sdk/bus/slack-conversation.d.ts +1 -1
- package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/cli/rows.d.ts +8 -2
- package/dist/types/sdk/cli/session-cli.d.ts +2 -1
- package/dist/types/sdk/host/events.d.ts +1 -0
- package/dist/types/sdk/host/query/cursor.d.ts +4 -2
- package/dist/types/sdk/host/query/handlers.d.ts +1 -0
- package/dist/types/sdk/host/query/revision-store.d.ts +1 -0
- package/dist/types/sdk/host/session-runtime.d.ts +18 -17
- package/dist/types/sdk/lifecycle-session.d.ts +1 -1
- package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -5
- package/dist/types/sdk/receipt-state.d.ts +2 -0
- package/dist/types/sdk/router/session-router.d.ts +4 -0
- package/dist/types/sdk/turn-result.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +47 -7
- package/dist/types/session/fold-coordinator.d.ts +29 -2
- package/dist/types/session/session-manager.d.ts +14 -2
- package/dist/types/tools/ask.d.ts +1 -0
- package/dist/types/tools/bash.d.ts +12 -0
- package/dist/types/tools/browser/launch.d.ts +10 -0
- package/dist/types/tools/browser/registry.d.ts +8 -0
- package/dist/types/tools/index.d.ts +6 -10
- package/dist/types/tools/job.d.ts +3 -1
- package/dist/types/utils/external-editor.d.ts +7 -0
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/pirate.ts +7 -4
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +7 -1
- package/src/async/job-manager.ts +107 -9
- package/src/browser-backend.ts +29 -0
- package/src/cli/plugin-cli.ts +29 -0
- package/src/cli/update-cli.ts +63 -12
- package/src/commands/sdk.ts +22 -7
- package/src/config/billing-path.ts +42 -0
- package/src/config/config-schema-version.ts +18 -1
- package/src/config/model-preset-registry.ts +177 -18
- package/src/config/model-registry.ts +268 -42
- package/src/config/settings-schema.ts +19 -7
- package/src/config/settings.ts +46 -15
- package/src/coordinator-mcp/projection-scan.ts +6 -6
- package/src/coordinator-mcp/server.ts +21 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/extensibility/extensions/runner.ts +27 -0
- package/src/extensibility/extensions/types.ts +8 -3
- package/src/extensibility/gjc-plugins/installer.ts +51 -11
- package/src/extensibility/runtime-skill-discovery.ts +110 -3
- package/src/extensibility/skills.ts +22 -12
- package/src/gjc-runtime/launch-worktree.ts +285 -4
- package/src/gjc-runtime/session-state-lock.ts +154 -28
- package/src/gjc-runtime/session-state-sidecar.ts +10 -17
- package/src/gjc-runtime/tmux-owner-isolation.ts +16 -1
- package/src/internal-urls/docs-index.generated.ts +10 -10
- package/src/main.ts +4 -1
- package/src/modes/acp/acp-agent.ts +5 -5
- package/src/modes/components/model-selector.ts +76 -8
- package/src/modes/components/session-selector.ts +23 -15
- package/src/modes/components/sessions-dashboard.ts +8 -5
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/event-controller.ts +30 -4
- package/src/modes/controllers/extension-ui-controller.ts +13 -13
- package/src/modes/controllers/input-controller.ts +2 -2
- package/src/modes/controllers/selector-controller.ts +10 -3
- package/src/modes/interactive-mode.ts +36 -12
- package/src/modes/prompt-action-autocomplete.ts +30 -4
- package/src/modes/runtime-init.ts +12 -9
- package/src/modes/theme/theme.ts +5 -0
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/ui-helpers.ts +22 -3
- package/src/prompts/agent-fragments/browser-aside.md +22 -0
- package/src/prompts/tools/ask.md +1 -1
- package/src/prompts/tools/bash.md +1 -0
- package/src/prompts/tools/job.md +1 -1
- package/src/sdk/acp/adapter.ts +5 -0
- package/src/sdk/broker/broker.ts +10 -1
- package/src/sdk/broker/ensure.ts +76 -3
- package/src/sdk/broker/lifecycle-ledger.ts +6 -0
- package/src/sdk/broker/lifecycle.ts +254 -34
- package/src/sdk/broker/spawn-substrate.ts +45 -2
- package/src/sdk/broker/startup-budget.ts +144 -2
- package/src/sdk/broker/state-version.ts +17 -0
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +111 -24
- package/src/sdk/bus/discord-daemon.ts +50 -10
- package/src/sdk/bus/index.ts +33 -3
- package/src/sdk/bus/kind-aware-reconciliation.ts +296 -48
- package/src/sdk/bus/lifecycle-commands.ts +4 -0
- package/src/sdk/bus/notification-service.ts +22 -5
- package/src/sdk/bus/prompt-reconciliation.ts +7 -2
- package/src/sdk/bus/reconciliation-store.ts +55 -31
- package/src/sdk/bus/slack-conversation.ts +1 -1
- package/src/sdk/bus/slack-daemon.ts +72 -38
- package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/cli/rows.ts +30 -7
- package/src/sdk/cli/session-cli.ts +33 -21
- package/src/sdk/host/control/dispatch.ts +2 -2
- package/src/sdk/host/events.ts +1 -0
- package/src/sdk/host/host.ts +2 -0
- package/src/sdk/host/query/cursor.ts +52 -7
- package/src/sdk/host/query/handlers.ts +33 -12
- package/src/sdk/host/query/revision-store.ts +12 -0
- package/src/sdk/host/session-runtime.test.ts +463 -8
- package/src/sdk/host/session-runtime.ts +383 -54
- package/src/sdk/host/surface-policy.ts +3 -3
- package/src/sdk/lifecycle-session.ts +7 -2
- package/src/sdk/prompt-deadline-manager.ts +2 -1
- package/src/sdk/protocol/operation-inventory.generated.json +54 -3
- package/src/sdk/protocol/operation-registry.ts +1 -1
- package/src/sdk/receipt-state.ts +10 -0
- package/src/sdk/router/session-router.ts +152 -59
- package/src/sdk/session.ts +60 -41
- package/src/sdk/turn-result.ts +22 -0
- package/src/session/agent-session.ts +1033 -260
- package/src/session/fold-coordinator.ts +40 -3
- package/src/session/session-manager.ts +517 -77
- package/src/slash-commands/builtin-registry.ts +41 -0
- package/src/task/executor.ts +9 -4
- package/src/tools/ask-contract.test.ts +64 -0
- package/src/tools/ask-contract.ts +36 -1
- package/src/tools/ask.ts +10 -8
- package/src/tools/bash-interactive.ts +11 -2
- package/src/tools/bash.ts +156 -33
- package/src/tools/browser/launch.ts +37 -0
- package/src/tools/browser/registry.ts +99 -9
- package/src/tools/cron.ts +6 -4
- package/src/tools/descriptors.test.ts +15 -0
- package/src/tools/descriptors.ts +5 -1
- package/src/tools/index.ts +6 -6
- package/src/tools/job.ts +4 -1
- package/src/tools/monitor.ts +12 -9
- package/src/tools/resolve.ts +30 -10
- package/src/tools/tool-catalog.generated.ts +2 -2
- package/src/utils/external-editor.ts +72 -1
package/src/tools/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AgentTelemetryConfig, AgentTool } from "@gajae-code/agent-core";
|
|
2
2
|
import type { Model, ServiceTier, ToolChoice } from "@gajae-code/ai/core";
|
|
3
3
|
import { $env, logger } from "@gajae-code/utils";
|
|
4
|
-
import type { AsyncJobManager } from "../async";
|
|
4
|
+
import type { AsyncJobManager, FoldReason } from "../async";
|
|
5
5
|
import type { PromptTemplate } from "../config/prompt-templates";
|
|
6
6
|
import type { Settings } from "../config/settings";
|
|
7
7
|
import type { Skill } from "../extensibility/skills";
|
|
@@ -240,12 +240,14 @@ export interface ToolSession {
|
|
|
240
240
|
registerForegroundFoldParticipant?: (adapter: FoldAdapter) => () => void;
|
|
241
241
|
/** Whether a foreground wait is currently foldable into a background job. */
|
|
242
242
|
hasForegroundBashBackgroundRequestHandler?: () => boolean;
|
|
243
|
-
/** Request that
|
|
244
|
-
requestForegroundBashBackground?: () => Promise<boolean>;
|
|
243
|
+
/** Request that a foreground wait fold into a background job, if supported. `reason` names the trigger; `adapter` targets a specific wait instead of the newest registration. */
|
|
244
|
+
requestForegroundBashBackground?: (reason?: FoldReason, adapter?: FoldAdapter) => Promise<boolean>;
|
|
245
|
+
/** Current tool interrupt policy; `finish_tools` lets the running tool finish, so a steer must not fold it. */
|
|
246
|
+
getToolInterruptPolicy?: () => "abort_tools" | "finish_tools";
|
|
245
247
|
|
|
246
248
|
/** Get the session-owned or inherited async job manager. */
|
|
247
249
|
getAsyncJobManager?: () => AsyncJobManager | undefined;
|
|
248
|
-
/** Resolves
|
|
250
|
+
/** Resolves on a steer admitted after the wait starts, or when `signal` aborts, without consuming the steer. */
|
|
249
251
|
waitForUserSteering?: (signal: AbortSignal) => Promise<void>;
|
|
250
252
|
/** Get session ID */
|
|
251
253
|
getSessionId?: () => string | null;
|
|
@@ -379,8 +381,6 @@ export interface ToolSession {
|
|
|
379
381
|
buildToolChoice?(toolName: string): ToolChoice | undefined;
|
|
380
382
|
/** Build a named tool-choice decision, preserving whether exact named forcing survived capability degradation. */
|
|
381
383
|
buildToolChoiceResult?(toolName: string): import("../utils/tool-choice").NamedToolChoiceResult;
|
|
382
|
-
/** Steer a hidden custom message into the conversation (e.g. a preview reminder). */
|
|
383
|
-
steer?(message: { customType: string; content: string; details?: unknown }): void;
|
|
384
384
|
/** Peek the currently in-flight tool-choice queue directive's invocation handler. Used by the `resolve` tool to dispatch to the pending action. */
|
|
385
385
|
peekQueueInvoker?(): ((input: unknown) => Promise<unknown> | unknown) | undefined;
|
|
386
386
|
/** Peek the long-lived "standing" resolve handler registered by a mode (e.g. plan mode).
|
package/src/tools/job.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { Component } from "@gajae-code/tui";
|
|
|
3
3
|
import { Text } from "@gajae-code/tui";
|
|
4
4
|
import { prompt } from "@gajae-code/utils";
|
|
5
5
|
import * as z from "zod/v4";
|
|
6
|
-
import { type AsyncJob, AsyncJobManager, isBackgroundJobSupportEnabled, jobElapsedMs } from "../async";
|
|
6
|
+
import { type AsyncJob, AsyncJobManager, type FoldReason, isBackgroundJobSupportEnabled, jobElapsedMs } from "../async";
|
|
7
7
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
8
8
|
import type { Theme } from "../modes/theme/theme";
|
|
9
9
|
import jobDescription from "../prompts/tools/job.md" with { type: "text" };
|
|
@@ -50,6 +50,8 @@ interface JobSnapshot {
|
|
|
50
50
|
status: "running" | "completed" | "failed" | "cancelled";
|
|
51
51
|
label: string;
|
|
52
52
|
durationMs: number;
|
|
53
|
+
/** Present when the job was folded out of a foreground wait. */
|
|
54
|
+
foldReason?: FoldReason;
|
|
53
55
|
resultText?: string;
|
|
54
56
|
errorText?: string;
|
|
55
57
|
}
|
|
@@ -344,6 +346,7 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
|
|
|
344
346
|
status: latest.status as JobSnapshot["status"],
|
|
345
347
|
label: latest.label,
|
|
346
348
|
durationMs: jobElapsedMs(latest, now),
|
|
349
|
+
...(current?.metadata?.foldReason ? { foldReason: current.metadata.foldReason } : {}),
|
|
347
350
|
...(latest.resultText ? { resultText: latest.resultText } : {}),
|
|
348
351
|
...(latest.errorText ? { errorText: latest.errorText } : {}),
|
|
349
352
|
};
|
package/src/tools/monitor.ts
CHANGED
|
@@ -229,19 +229,22 @@ export class MonitorTool implements AgentTool<typeof monitorSchema, MonitorToolD
|
|
|
229
229
|
);
|
|
230
230
|
pendingNotifications = MAX_PENDING_MONITOR_NOTIFICATIONS;
|
|
231
231
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
232
|
+
if (!this.session.sendCustomMessage) {
|
|
233
|
+
logger.warn("Monitor task-notification dropped: session has no custom-message router", {
|
|
234
|
+
taskId: jobId,
|
|
235
|
+
});
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
void this.session
|
|
239
|
+
.sendCustomMessage(
|
|
240
|
+
{ customType: "task-notification", content, display: false, attribution: "agent", details },
|
|
241
|
+
{ triggerTurn: true, deliverAs: "followUp" },
|
|
242
|
+
)
|
|
243
|
+
.catch(error => {
|
|
238
244
|
logger.warn("Monitor task-notification delivery failed", {
|
|
239
245
|
error: error instanceof Error ? error.message : String(error),
|
|
240
246
|
});
|
|
241
247
|
});
|
|
242
|
-
} else {
|
|
243
|
-
this.session.steer?.({ customType: "task-notification", content, details });
|
|
244
|
-
}
|
|
245
248
|
};
|
|
246
249
|
const schedulePersistentNotification = (line: string) => {
|
|
247
250
|
latestLine = line;
|
package/src/tools/resolve.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
|
|
2
2
|
import type { ToolChoice } from "@gajae-code/ai/core";
|
|
3
3
|
import type { Component } from "@gajae-code/tui";
|
|
4
4
|
import { Text } from "@gajae-code/tui";
|
|
5
|
-
import { prompt, untilAborted } from "@gajae-code/utils";
|
|
5
|
+
import { logger, prompt, untilAborted } from "@gajae-code/utils";
|
|
6
6
|
import * as z from "zod/v4";
|
|
7
7
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
8
8
|
import type { Theme } from "../modes/theme/theme";
|
|
@@ -66,15 +66,35 @@ export function queueResolveHandler(
|
|
|
66
66
|
const queue = session.getToolChoiceQueue?.();
|
|
67
67
|
|
|
68
68
|
const steerReminder = (): void => {
|
|
69
|
-
session.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
if (!session.sendCustomMessage) {
|
|
70
|
+
// The reminder is a degraded model's only model-visible route back to the
|
|
71
|
+
// standing resolve handler; a missing router must be visible.
|
|
72
|
+
logger.warn("Resolve reminder dropped: session has no custom-message router", {
|
|
73
|
+
toolName: options.sourceToolName,
|
|
74
|
+
});
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
void session
|
|
78
|
+
.sendCustomMessage(
|
|
79
|
+
{
|
|
80
|
+
customType: "resolve-reminder",
|
|
81
|
+
content: [
|
|
82
|
+
"<system-reminder>",
|
|
83
|
+
"This is a preview. Call the `resolve` tool to apply or discard these changes.",
|
|
84
|
+
"</system-reminder>",
|
|
85
|
+
].join("\n"),
|
|
86
|
+
display: false,
|
|
87
|
+
attribution: "agent",
|
|
88
|
+
details: { toolName: options.sourceToolName },
|
|
89
|
+
},
|
|
90
|
+
{ deliverAs: "steer" },
|
|
91
|
+
)
|
|
92
|
+
.catch(error => {
|
|
93
|
+
logger.warn("Resolve reminder delivery failed", {
|
|
94
|
+
toolName: options.sourceToolName,
|
|
95
|
+
error: error instanceof Error ? error.message : String(error),
|
|
96
|
+
});
|
|
97
|
+
});
|
|
78
98
|
};
|
|
79
99
|
|
|
80
100
|
// Re-evaluated on every push (including apply-error re-pushes) so a runtime
|
|
@@ -283,7 +283,7 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
|
|
|
283
283
|
"ask": {
|
|
284
284
|
"name": "ask",
|
|
285
285
|
"label": "Ask",
|
|
286
|
-
"description": "Asks user when you need clarification or input during task execution.\n\n<conditions>\n- Multiple approaches exist with significantly different tradeoffs user should weigh\n</conditions>\n\n<instruction>\n- Use `recommended: <index>` to mark default (0-indexed); \" (Recommended)\" added automatically\n- Use `questions` for multiple related questions instead of asking one at a time\n- Set `multi: true` on question to allow multiple selections\n</instruction>\n\n<caution>\n- Provide 2-5 concise, distinct options\n</caution>\n\n<critical>\n- **Default to action.** Resolve ambiguity yourself using repo conventions, existing patterns, and reasonable defaults. Exhaust existing sources (code, configs, docs, history) before asking. Only ask when options have materially different tradeoffs the user must decide.\n- **If multiple choices are acceptable**, pick the most conservative/standard option and proceed; state the choice.\n- **Do NOT include \"Other\"
|
|
286
|
+
"description": "Asks user when you need clarification or input during task execution.\n\n<conditions>\n- Multiple approaches exist with significantly different tradeoffs user should weigh\n</conditions>\n\n<instruction>\n- Use `recommended: <index>` to mark default (0-indexed); \" (Recommended)\" added automatically\n- Use `questions` for multiple related questions instead of asking one at a time\n- Set `multi: true` on question to allow multiple selections\n</instruction>\n\n<caution>\n- Provide 2-5 concise, distinct options\n</caution>\n\n<critical>\n- **Default to action.** Resolve ambiguity yourself using repo conventions, existing patterns, and reasonable defaults. Exhaust existing sources (code, configs, docs, history) before asking. Only ask when options have materially different tradeoffs the user must decide.\n- **If multiple choices are acceptable**, pick the most conservative/standard option and proceed; state the choice.\n- **Do NOT include the automatic custom-input control or labels that imitate it** such as \"Other (type your own)\", \"Other (specify)\", \"type your own\", \"custom input\", or \"직접 입력\" — UI automatically adds \"Other (type your own)\" to every question. A plain domain value such as \"Other\" is allowed when it is a genuine answer, not a request for free text. To collect free text, ask for it in the question body and rely on the automatic Other/custom-input control instead of adding a fake option.\n</critical>\n\n<examples>\n# Single question\nquestions: [{\"id\": \"auth_method\", \"question\": \"Which authentication method should this API use?\", \"options\": [{\"label\": \"JWT\"}, {\"label\": \"OAuth2\"}, {\"label\": \"Session cookies\"}], \"recommended\": 0}]\n\n# Multiple questions\nquestions: [{\"id\": \"storage_type\", \"question\": \"Which storage backend?\", \"options\": [{\"label\": \"SQLite\"}, {\"label\": \"PostgreSQL\"}]}, {\"id\": \"auth_method\", \"question\": \"Which auth method?\", \"options\": [{\"label\": \"JWT\"}, {\"label\": \"Session cookies\"}]}]\n</examples>",
|
|
287
287
|
"parameters": {
|
|
288
288
|
"type": "object",
|
|
289
289
|
"properties": {
|
|
@@ -2319,7 +2319,7 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
|
|
|
2319
2319
|
"job": {
|
|
2320
2320
|
"name": "job",
|
|
2321
2321
|
"label": "Job",
|
|
2322
|
-
"description": "Inspects, waits, or cancels async jobs.\n\nBackground job results are delivered automatically when complete. Jobs that back task subagents should be controlled via the `subagent` tool when it is available; use `job` for non-subagent jobs (async bash, monitors) and as a compatibility fallback. Running job output stays quiet by default to avoid flooding the conversation; use `tail` when you explicitly want to show/reopen retained output. Reach for this tool only when you need to inspect or intervene.\n\nIn the interactive TUI, a running foreground command can be folded into a background job by pressing the fold chord twice (default `Alt+Shift+B`, plus `Cmd+B` on macOS; remappable via `app.tool.backgroundFold`). This covers managed non-PTY bash, editor/ACP client-terminal commands, and PTY-mode commands. A folded job keeps its original deadline, is never killed or restarted by folding, and its result wakes a later turn automatically; a folded PTY continues output-only and no longer accepts input. `task`/`subagent` waits are not foldable. Raw shell `Ctrl+Z`/`bg` is not the supported path inside GJC because it bypasses job ownership and output-routing contracts.\n\n# Operations\n\n## `list: true`\nUse to inspect what's running.\n\n## `tail: [id, …]`\nShow the retained output buffer for one or more background jobs without waiting.\n- Use this to reopen/tail a backgrounded long-running bash/tool output after folding it away.\n- Output is bounded by the manager retention window; stale cursors may report that only the retained tail is available.\n- Prefer `tail` over polling when you only need to peek at progress, so the conversation can continue without flooding the TUI.\n\n## `poll: [id, …]`\nBlock until the specified jobs finish or the wait window (~30 s, not configurable) elapses.\n- Use when you are genuinely blocked on a result and have no other work to do.\n- Returns the current snapshot when the timer elapses; running jobs remain running.\n- Completed jobs include their final output in the returned snapshot.\n\n## `cancel: [id, …]`\nStop running jobs.\n- Use when a job is stalled, hung, or no longer needed.\n- Returns immediately after cancelling.",
|
|
2322
|
+
"description": "Inspects, waits, or cancels async jobs.\n\nBackground job results are delivered automatically when complete. Jobs that back task subagents should be controlled via the `subagent` tool when it is available; use `job` for non-subagent jobs (async bash, monitors) and as a compatibility fallback. Running job output stays quiet by default to avoid flooding the conversation; use `tail` when you explicitly want to show/reopen retained output. Reach for this tool only when you need to inspect or intervene.\n\nIn the interactive TUI, a running foreground command can be folded into a background job by pressing the fold chord twice (default `Alt+Shift+B`, plus `Cmd+B` on macOS; remappable via `app.tool.backgroundFold`), or automatically when the user sends a steer after the command has run for at least 2 seconds (with `busyPromptMode=steer` and `toolInterruptPolicy=abort_tools`). This covers managed non-PTY bash, editor/ACP client-terminal commands, and PTY-mode commands. A folded job keeps its original deadline, is never killed or restarted by folding, and its result wakes a later turn automatically; a folded PTY continues output-only and no longer accepts input. Job listings expose `foldReason` (`steer`, `chord`, `timer`, or `sdk_control`) beside `backgrounded` so you can tell why a job left the foreground. `task`/`subagent` waits are not foldable. Raw shell `Ctrl+Z`/`bg` is not the supported path inside GJC because it bypasses job ownership and output-routing contracts.\n\n# Operations\n\n## `list: true`\nUse to inspect what's running.\n\n## `tail: [id, …]`\nShow the retained output buffer for one or more background jobs without waiting.\n- Use this to reopen/tail a backgrounded long-running bash/tool output after folding it away.\n- Output is bounded by the manager retention window; stale cursors may report that only the retained tail is available.\n- Prefer `tail` over polling when you only need to peek at progress, so the conversation can continue without flooding the TUI.\n\n## `poll: [id, …]`\nBlock until the specified jobs finish or the wait window (~30 s, not configurable) elapses.\n- Use when you are genuinely blocked on a result and have no other work to do.\n- Returns the current snapshot when the timer elapses; running jobs remain running.\n- Completed jobs include their final output in the returned snapshot.\n\n## `cancel: [id, …]`\nStop running jobs.\n- Use when a job is stalled, hung, or no longer needed.\n- Returns immediately after cancelling.",
|
|
2323
2323
|
"parameters": {
|
|
2324
2324
|
"type": "object",
|
|
2325
2325
|
"properties": {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Utilities for launching an external text editor ($VISUAL / $EDITOR).
|
|
3
3
|
*/
|
|
4
|
+
import { dlopen, FFIType, ptr } from "bun:ffi";
|
|
4
5
|
import { spawn } from "node:child_process";
|
|
5
6
|
import * as fs from "node:fs/promises";
|
|
6
7
|
import * as os from "node:os";
|
|
@@ -26,6 +27,71 @@ export function trimEditorTrailingNewline(text: string): string {
|
|
|
26
27
|
return text.replace(/\r?\n$/, "");
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Native Windows editor executables must bypass cmd.exe. The shell wrapper
|
|
32
|
+
* reclaims the shared console input buffer while it waits for the editor,
|
|
33
|
+
* reopening the cooked-mode window that loses the first keystroke. Batch
|
|
34
|
+
* files still require a shell on Windows; ordinary executables do not.
|
|
35
|
+
*/
|
|
36
|
+
export function shouldUseExternalEditorShell(editor: string, platform: NodeJS.Platform = process.platform): boolean {
|
|
37
|
+
return platform === "win32" && /\.(?:cmd|bat)$/iu.test(editor);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* On Windows the console input buffer is shared between the parent and the
|
|
42
|
+
* child. When the parent TUI stops it restores the console to cooked mode
|
|
43
|
+
* (ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT) and disables
|
|
44
|
+
* ENABLE_VIRTUAL_TERMINAL_INPUT. A child TUI such as Neovim then inherits
|
|
45
|
+
* that cooked state: the very next keystroke is line-buffered until an
|
|
46
|
+
* Enter flushes it — exactly the "first i is swallowed, Enter fixes it"
|
|
47
|
+
* symptom reported in #5224.
|
|
48
|
+
*
|
|
49
|
+
* Neovim (via libuv's uv_tty) re-enables raw VT input on startup, but the
|
|
50
|
+
* race window between the parent's restore and the child's init is enough
|
|
51
|
+
* to deterministically line-buffer the first typed keystroke when the child
|
|
52
|
+
* is spawned through `cmd.exe` (`shell: true`), because `cmd` itself keeps
|
|
53
|
+
* the console in cooked mode while it waits for the child.
|
|
54
|
+
*
|
|
55
|
+
* To make the handover symmetric:
|
|
56
|
+
* - flush any pending console input left over from the parent (e.g. probe
|
|
57
|
+
* reply fragments that were already read into the kernel buffer), and
|
|
58
|
+
* - leave the console in raw VT mode (VT on, LINE/ECHO off) so the child
|
|
59
|
+
* inherits an immediately-usable mode; the caller's `ui.start()` will
|
|
60
|
+
* reconcile the mode again when the editor exits.
|
|
61
|
+
*/
|
|
62
|
+
const STD_INPUT_HANDLE = -10;
|
|
63
|
+
const ENABLE_LINE_INPUT = 0x0002;
|
|
64
|
+
const ENABLE_ECHO_INPUT = 0x0004;
|
|
65
|
+
const ENABLE_WINDOW_INPUT = 0x0008;
|
|
66
|
+
const ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200;
|
|
67
|
+
|
|
68
|
+
function prepareWindowsConsoleForExternalEditor(): void {
|
|
69
|
+
if (process.platform !== "win32") return;
|
|
70
|
+
try {
|
|
71
|
+
const kernel32 = dlopen("kernel32.dll", {
|
|
72
|
+
GetStdHandle: { args: [FFIType.i32], returns: FFIType.ptr },
|
|
73
|
+
GetConsoleMode: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.bool },
|
|
74
|
+
SetConsoleMode: { args: [FFIType.ptr, FFIType.u32], returns: FFIType.bool },
|
|
75
|
+
FlushConsoleInputBuffer: { args: [FFIType.ptr], returns: FFIType.bool },
|
|
76
|
+
});
|
|
77
|
+
const handle = kernel32.symbols.GetStdHandle(STD_INPUT_HANDLE);
|
|
78
|
+
try {
|
|
79
|
+
kernel32.symbols.FlushConsoleInputBuffer(handle);
|
|
80
|
+
} catch {}
|
|
81
|
+
const mode = new Uint32Array(1);
|
|
82
|
+
const modePtr = ptr(mode);
|
|
83
|
+
if (modePtr && kernel32.symbols.GetConsoleMode(handle, modePtr)) {
|
|
84
|
+
const cur = mode[0]!;
|
|
85
|
+
const next =
|
|
86
|
+
(cur | ENABLE_VIRTUAL_TERMINAL_INPUT | ENABLE_WINDOW_INPUT) & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);
|
|
87
|
+
if (next !== cur) {
|
|
88
|
+
kernel32.symbols.SetConsoleMode(handle, next);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
kernel32.close();
|
|
92
|
+
} catch {}
|
|
93
|
+
}
|
|
94
|
+
|
|
29
95
|
/**
|
|
30
96
|
* Opens `content` in the user's external editor and returns the edited text.
|
|
31
97
|
* Returns `null` if the editor exits with a non-zero code.
|
|
@@ -46,7 +112,12 @@ export async function openInEditor(
|
|
|
46
112
|
const [editor, ...editorArgs] = editorCmd.split(" ");
|
|
47
113
|
const stdio = options?.stdio ?? ["inherit", "inherit", "inherit"];
|
|
48
114
|
|
|
49
|
-
|
|
115
|
+
prepareWindowsConsoleForExternalEditor();
|
|
116
|
+
|
|
117
|
+
const child = spawn(editor, [...editorArgs, tmpFile], {
|
|
118
|
+
stdio,
|
|
119
|
+
shell: shouldUseExternalEditorShell(editor),
|
|
120
|
+
});
|
|
50
121
|
const { promise, reject, resolve } = Promise.withResolvers<number>();
|
|
51
122
|
child.once("exit", (code, signal) => resolve(code ?? (signal ? -1 : 0)));
|
|
52
123
|
child.once("error", error => reject(error));
|