@gajae-code/coding-agent 0.4.5 → 0.5.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 +62 -0
- package/dist/types/async/job-manager.d.ts +26 -0
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli/list-models.d.ts +6 -0
- package/dist/types/commands/gc.d.ts +26 -0
- package/dist/types/commands/harness.d.ts +3 -0
- package/dist/types/config/file-lock-gc.d.ts +5 -0
- package/dist/types/config/file-lock.d.ts +7 -0
- package/dist/types/config/model-profile-activation.d.ts +11 -2
- package/dist/types/config/model-profiles.d.ts +7 -0
- package/dist/types/config/model-registry.d.ts +3 -0
- package/dist/types/config/model-resolver.d.ts +2 -0
- package/dist/types/config/models-config-schema.d.ts +30 -0
- package/dist/types/config/settings-schema.d.ts +4 -3
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/defaults/gjc/extensions/grok-build/index.d.ts +1 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/index.d.ts +1 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.d.ts +25 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.d.ts +27 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/billing.d.ts +8 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.d.ts +5 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +10 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/usage.d.ts +2 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.d.ts +2 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/shared/errors.d.ts +38 -0
- package/dist/types/defaults/gjc-grok-cli.d.ts +5 -0
- package/dist/types/extensibility/extensions/index.d.ts +1 -0
- package/dist/types/extensibility/extensions/prefix-command-bridge.d.ts +35 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +103 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +112 -0
- package/dist/types/gjc-runtime/gc-render.d.ts +6 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +134 -0
- package/dist/types/gjc-runtime/ledger-event-renderer.d.ts +68 -0
- package/dist/types/gjc-runtime/team-gc.d.ts +7 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +5 -1
- package/dist/types/gjc-runtime/tmux-common.d.ts +14 -0
- package/dist/types/gjc-runtime/tmux-gc.d.ts +7 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +13 -0
- package/dist/types/harness-control-plane/gc-adapter.d.ts +3 -0
- package/dist/types/harness-control-plane/owner.d.ts +8 -1
- package/dist/types/harness-control-plane/receipt-spool.d.ts +19 -0
- package/dist/types/harness-control-plane/state-machine.d.ts +6 -1
- package/dist/types/harness-control-plane/storage.d.ts +20 -0
- package/dist/types/harness-control-plane/types.d.ts +4 -0
- package/dist/types/hindsight/mental-models.d.ts +5 -5
- package/dist/types/modes/components/hook-selector.d.ts +7 -1
- package/dist/types/modes/components/model-selector.d.ts +1 -12
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +2 -2
- package/dist/types/modes/rpc/rpc-mode.d.ts +16 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +4 -1
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +13 -0
- package/dist/types/modes/shared/agent-wire/session-registry.d.ts +25 -0
- package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +2 -0
- package/dist/types/sdk.d.ts +5 -0
- package/dist/types/session/agent-session.d.ts +3 -1
- package/dist/types/session/blob-store.d.ts +59 -4
- package/dist/types/session/session-manager.d.ts +24 -6
- package/dist/types/session/streaming-output.d.ts +3 -2
- package/dist/types/session/tool-choice-queue.d.ts +6 -0
- package/dist/types/skill-state/workflow-hud.d.ts +14 -0
- package/dist/types/task/receipt.d.ts +1 -0
- package/dist/types/task/types.d.ts +7 -0
- package/dist/types/thinking-metadata.d.ts +16 -0
- package/dist/types/thinking.d.ts +3 -12
- package/dist/types/tools/ask.d.ts +15 -1
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/resolve.d.ts +0 -10
- package/dist/types/tools/subagent.d.ts +6 -0
- package/dist/types/utils/tool-choice.d.ts +14 -1
- package/package.json +7 -7
- package/src/async/job-manager.ts +52 -0
- package/src/cli/args.ts +3 -0
- package/src/cli/auth-broker-cli.ts +1 -0
- package/src/cli/list-models.ts +13 -1
- package/src/cli.ts +9 -4
- package/src/commands/gc.ts +22 -0
- package/src/commands/harness.ts +43 -5
- package/src/commands/launch.ts +2 -2
- package/src/commands/session.ts +3 -1
- package/src/config/file-lock-gc.ts +181 -0
- package/src/config/file-lock.ts +14 -0
- package/src/config/model-profile-activation.ts +15 -3
- package/src/config/model-profiles.ts +264 -56
- package/src/config/model-resolver.ts +9 -6
- package/src/config/models-config-schema.ts +1 -0
- package/src/config/settings-schema.ts +6 -3
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/server.ts +513 -26
- package/src/cursor.ts +16 -2
- package/src/defaults/gjc/agent.models.grok-cli.yml +36 -0
- package/src/defaults/gjc/extensions/grok-build/index.ts +1 -0
- package/src/defaults/gjc/extensions/grok-build/package.json +7 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/biome.json +39 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/package.json +8 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/index.ts +1 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +155 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.ts +361 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/billing.ts +57 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +99 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +50 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/usage.ts +56 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +36 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/errors.ts +44 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +131 -113
- package/src/defaults/gjc/skills/deep-interview/lateral-review-panel.md +49 -0
- package/src/defaults/gjc/skills/team/SKILL.md +3 -2
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +8 -2
- package/src/defaults/gjc-defaults.ts +7 -0
- package/src/defaults/gjc-grok-cli.ts +22 -0
- package/src/export/html/index.ts +13 -9
- package/src/extensibility/extensions/index.ts +1 -0
- package/src/extensibility/extensions/prefix-command-bridge.ts +128 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +417 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +18 -26
- package/src/gjc-runtime/deep-interview-state.ts +324 -0
- package/src/gjc-runtime/gc-render.ts +70 -0
- package/src/gjc-runtime/gc-runtime.ts +403 -0
- package/src/gjc-runtime/ledger-event-renderer.ts +164 -0
- package/src/gjc-runtime/ralplan-runtime.ts +58 -7
- package/src/gjc-runtime/state-renderer.ts +12 -3
- package/src/gjc-runtime/state-runtime.ts +46 -29
- package/src/gjc-runtime/team-gc.ts +49 -0
- package/src/gjc-runtime/team-runtime.ts +211 -8
- package/src/gjc-runtime/tmux-common.ts +29 -0
- package/src/gjc-runtime/tmux-gc.ts +176 -0
- package/src/gjc-runtime/tmux-sessions.ts +68 -12
- package/src/gjc-runtime/ultragoal-runtime.ts +517 -41
- package/src/gjc-runtime/workflow-manifest.generated.json +27 -1
- package/src/gjc-runtime/workflow-manifest.ts +16 -1
- package/src/harness-control-plane/gc-adapter.ts +184 -0
- package/src/harness-control-plane/owner.ts +89 -27
- package/src/harness-control-plane/receipt-spool.ts +128 -0
- package/src/harness-control-plane/state-machine.ts +27 -6
- package/src/harness-control-plane/storage.ts +93 -0
- package/src/harness-control-plane/types.ts +4 -0
- package/src/hindsight/mental-models.ts +17 -16
- package/src/internal-urls/docs-index.generated.ts +14 -8
- package/src/main.ts +7 -2
- package/src/modes/components/assistant-message.ts +26 -14
- package/src/modes/components/diff.ts +97 -0
- package/src/modes/components/hook-selector.ts +19 -0
- package/src/modes/components/model-selector.ts +370 -181
- package/src/modes/components/status-line/segments.ts +1 -1
- package/src/modes/components/tool-execution.ts +30 -13
- package/src/modes/controllers/command-controller.ts +25 -6
- package/src/modes/controllers/extension-ui-controller.ts +3 -0
- package/src/modes/controllers/selector-controller.ts +34 -42
- package/src/modes/rpc/rpc-client.ts +3 -2
- package/src/modes/rpc/rpc-mode.ts +187 -39
- package/src/modes/rpc/rpc-types.ts +5 -2
- package/src/modes/shared/agent-wire/command-dispatch.ts +279 -257
- package/src/modes/shared/agent-wire/command-validation.ts +11 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +30 -1
- package/src/modes/shared/agent-wire/session-registry.ts +109 -0
- package/src/modes/shared/agent-wire/unattended-action-policy.ts +24 -0
- package/src/modes/shared/agent-wire/unattended-run-controller.ts +23 -3
- package/src/modes/shared/agent-wire/unattended-session.ts +16 -1
- package/src/sdk.ts +46 -5
- package/src/secrets/obfuscator.ts +102 -27
- package/src/session/agent-session.ts +179 -25
- package/src/session/blob-store.ts +148 -6
- package/src/session/session-manager.ts +311 -60
- package/src/session/streaming-output.ts +185 -122
- package/src/session/tool-choice-queue.ts +23 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +7 -1
- package/src/skill-state/workflow-hud.ts +106 -10
- package/src/slash-commands/builtin-registry.ts +3 -2
- package/src/task/executor.ts +78 -6
- package/src/task/receipt.ts +5 -0
- package/src/task/render.ts +21 -1
- package/src/task/types.ts +8 -0
- package/src/thinking-metadata.ts +51 -0
- package/src/thinking.ts +26 -46
- package/src/tools/ask.ts +56 -1
- package/src/tools/bash.ts +1 -1
- package/src/tools/index.ts +2 -0
- package/src/tools/job.ts +3 -2
- package/src/tools/monitor.ts +36 -1
- package/src/tools/resolve.ts +93 -18
- package/src/tools/subagent-render.ts +9 -0
- package/src/tools/subagent.ts +26 -2
- package/src/utils/edit-mode.ts +1 -1
- package/src/utils/tool-choice.ts +45 -16
package/src/tools/ask.ts
CHANGED
|
@@ -34,6 +34,8 @@ import {
|
|
|
34
34
|
renderDeepInterviewAskQuestion,
|
|
35
35
|
} from "../deep-interview/render-middleware";
|
|
36
36
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
37
|
+
import { appendOrMergeDeepInterviewRound } from "../gjc-runtime/deep-interview-recorder";
|
|
38
|
+
import { deepInterviewStatePath } from "../gjc-runtime/deep-interview-runtime";
|
|
37
39
|
import { gateAnswerToResult, questionToGate } from "../modes/shared/agent-wire/deep-interview-gate";
|
|
38
40
|
import { getMarkdownTheme, type Theme, theme } from "../modes/theme/theme";
|
|
39
41
|
import askDescription from "../prompts/tools/ask.md" with { type: "text" };
|
|
@@ -50,15 +52,25 @@ const OptionItem = z.object({
|
|
|
50
52
|
label: z.string().describe("display label"),
|
|
51
53
|
});
|
|
52
54
|
|
|
55
|
+
/** Optional structured deep-interview round metadata; when present the round is recorded automatically. */
|
|
56
|
+
const DeepInterviewMeta = z.object({
|
|
57
|
+
round_id: z.string().describe("stable optional round identity").optional(),
|
|
58
|
+
round: z.number().int().nonnegative().describe("round number"),
|
|
59
|
+
component: z.string().min(1).describe("targeted topology component"),
|
|
60
|
+
dimension: z.string().min(1).describe("targeted clarity dimension"),
|
|
61
|
+
ambiguity: z.number().min(0).max(1).describe("ambiguity at ask time (0..1)"),
|
|
62
|
+
});
|
|
63
|
+
|
|
53
64
|
const QuestionItem = z.object({
|
|
54
65
|
id: z.string().describe("question id"),
|
|
55
66
|
question: z.string().describe("question text"),
|
|
56
67
|
options: z.array(OptionItem).describe("available options"),
|
|
57
68
|
multi: z.boolean().describe("allow multiple selections").optional(),
|
|
58
69
|
recommended: z.number().describe("recommended option index").optional(),
|
|
70
|
+
deepInterview: DeepInterviewMeta.describe("optional deep-interview round metadata").optional(),
|
|
59
71
|
});
|
|
60
72
|
|
|
61
|
-
const askSchema = z.object({
|
|
73
|
+
export const askSchema = z.object({
|
|
62
74
|
questions: z.array(QuestionItem).min(1).describe("questions to ask"),
|
|
63
75
|
});
|
|
64
76
|
|
|
@@ -456,6 +468,45 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
456
468
|
TERMINAL.sendNotification("Waiting for input");
|
|
457
469
|
}
|
|
458
470
|
|
|
471
|
+
/**
|
|
472
|
+
* Record a resolved deep-interview round when the question carries structured
|
|
473
|
+
* metadata. The runtime owns durable record/merge semantics; this tool is only the
|
|
474
|
+
* caller. Best-effort: a state-write hiccup must not break the user's answer flow.
|
|
475
|
+
*/
|
|
476
|
+
async #recordDeepInterviewRound(
|
|
477
|
+
q: AskParams["questions"][number],
|
|
478
|
+
selectedOptions: string[],
|
|
479
|
+
customInput: string | undefined,
|
|
480
|
+
): Promise<void> {
|
|
481
|
+
const meta = q.deepInterview;
|
|
482
|
+
if (!meta) return;
|
|
483
|
+
try {
|
|
484
|
+
const cwd = this.session.cwd;
|
|
485
|
+
const sessionId = this.session.getSessionId?.() ?? undefined;
|
|
486
|
+
const statePath = deepInterviewStatePath(cwd, sessionId);
|
|
487
|
+
await appendOrMergeDeepInterviewRound(
|
|
488
|
+
cwd,
|
|
489
|
+
statePath,
|
|
490
|
+
{
|
|
491
|
+
round: meta.round,
|
|
492
|
+
round_id: meta.round_id,
|
|
493
|
+
questionId: q.id,
|
|
494
|
+
questionText: q.question,
|
|
495
|
+
component: meta.component,
|
|
496
|
+
dimension: meta.dimension,
|
|
497
|
+
ambiguity: meta.ambiguity,
|
|
498
|
+
selectedOptions,
|
|
499
|
+
customInput,
|
|
500
|
+
},
|
|
501
|
+
{ sessionId },
|
|
502
|
+
);
|
|
503
|
+
} catch (error) {
|
|
504
|
+
console.warn(
|
|
505
|
+
`ask: deep-interview round recording failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
459
510
|
async execute(
|
|
460
511
|
_toolCallId: string,
|
|
461
512
|
params: AskParams,
|
|
@@ -515,6 +566,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
515
566
|
options: q.options,
|
|
516
567
|
multi: q.multi,
|
|
517
568
|
recommended: q.recommended,
|
|
569
|
+
deepInterview: q.deepInterview,
|
|
518
570
|
};
|
|
519
571
|
const answer = await gateEmitter.emitGate(questionToGate(gateQuestion));
|
|
520
572
|
const decoded = gateAnswerToResult(gateQuestion, answer);
|
|
@@ -582,6 +634,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
582
634
|
context?.abort();
|
|
583
635
|
throw new ToolAbortError("Ask tool was cancelled by the user");
|
|
584
636
|
}
|
|
637
|
+
await this.#recordDeepInterviewRound(q, selectedOptions, customInput);
|
|
585
638
|
const details: AskToolDetails = {
|
|
586
639
|
question: q.question,
|
|
587
640
|
options: optionLabels,
|
|
@@ -644,6 +697,8 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
644
697
|
customInput,
|
|
645
698
|
};
|
|
646
699
|
|
|
700
|
+
await this.#recordDeepInterviewRound(q, selectedOptions, customInput);
|
|
701
|
+
|
|
647
702
|
if (navAction === "back") {
|
|
648
703
|
questionIndex = Math.max(0, questionIndex - 1);
|
|
649
704
|
continue;
|
package/src/tools/bash.ts
CHANGED
|
@@ -51,7 +51,7 @@ async function saveBashOriginalArtifact(session: ToolSession, originalText: stri
|
|
|
51
51
|
const bashSchemaBase = z.object({
|
|
52
52
|
command: z.string().describe("command to execute"),
|
|
53
53
|
env: z.record(z.string().regex(BASH_ENV_NAME_PATTERN), z.string()).optional().describe("extra env vars"),
|
|
54
|
-
timeout: z.number().default(300).describe("timeout in seconds").optional(),
|
|
54
|
+
timeout: z.number().default(300).describe("timeout in seconds, NOT milliseconds (30 = 30s)").optional(),
|
|
55
55
|
cwd: z.string().describe("working directory").optional(),
|
|
56
56
|
pty: z.boolean().describe("run in pty mode").optional(),
|
|
57
57
|
});
|
package/src/tools/index.ts
CHANGED
|
@@ -240,6 +240,8 @@ export interface ToolSession {
|
|
|
240
240
|
getToolChoiceQueue?(): ToolChoiceQueue;
|
|
241
241
|
/** Build a model-provider-specific ToolChoice that targets the named tool, or undefined if unsupported. */
|
|
242
242
|
buildToolChoice?(toolName: string): ToolChoice | undefined;
|
|
243
|
+
/** Build a named tool-choice decision, preserving whether exact named forcing survived capability degradation. */
|
|
244
|
+
buildToolChoiceResult?(toolName: string): import("../utils/tool-choice").NamedToolChoiceResult;
|
|
243
245
|
/** Steer a hidden custom message into the conversation (e.g. a preview reminder). */
|
|
244
246
|
steer?(message: { customType: string; content: string; details?: unknown }): void;
|
|
245
247
|
/** Peek the currently in-flight tool-choice queue directive's invocation handler. Used by the `resolve` tool to dispatch to the pending action. */
|
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 } from "../async";
|
|
6
|
+
import { type AsyncJob, AsyncJobManager, 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" };
|
|
@@ -257,6 +257,7 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
|
|
|
257
257
|
status: string;
|
|
258
258
|
label: string;
|
|
259
259
|
startTime: number;
|
|
260
|
+
endTime?: number;
|
|
260
261
|
resultText?: string;
|
|
261
262
|
errorText?: string;
|
|
262
263
|
}[],
|
|
@@ -270,7 +271,7 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
|
|
|
270
271
|
type: latest.type,
|
|
271
272
|
status: latest.status as JobSnapshot["status"],
|
|
272
273
|
label: latest.label,
|
|
273
|
-
durationMs:
|
|
274
|
+
durationMs: jobElapsedMs(latest, now),
|
|
274
275
|
...(latest.resultText ? { resultText: latest.resultText } : {}),
|
|
275
276
|
...(latest.errorText ? { errorText: latest.errorText } : {}),
|
|
276
277
|
};
|
package/src/tools/monitor.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { logger, prompt } from "@gajae-code/utils";
|
|
|
3
3
|
import * as z from "zod/v4";
|
|
4
4
|
import { AsyncJobManager, isBackgroundJobSupportEnabled } from "../async";
|
|
5
5
|
import monitorDescription from "../prompts/tools/monitor.md" with { type: "text" };
|
|
6
|
+
import { truncateTail } from "../session/streaming-output";
|
|
6
7
|
import { BashTool } from "./bash";
|
|
7
8
|
import type { ToolSession } from "./index";
|
|
8
9
|
import { ToolError } from "./tool-errors";
|
|
@@ -48,6 +49,8 @@ export interface MonitorToolDetails {
|
|
|
48
49
|
|
|
49
50
|
const MONITOR_LABEL_MAX = 120;
|
|
50
51
|
const MAX_PENDING_MONITOR_NOTIFICATIONS = 3;
|
|
52
|
+
const MONITOR_NOTIFICATION_LINE_MAX_BYTES = 16 * 1024;
|
|
53
|
+
const MONITOR_NOTIFICATION_LINE_MAX_LINES = 20;
|
|
51
54
|
|
|
52
55
|
function buildMonitorLabel(params: MonitorParams): string {
|
|
53
56
|
const base = `[monitor:${params.kind}] ${params.description}`;
|
|
@@ -55,6 +58,34 @@ function buildMonitorLabel(params: MonitorParams): string {
|
|
|
55
58
|
return `${base.slice(0, MONITOR_LABEL_MAX - 3)}...`;
|
|
56
59
|
}
|
|
57
60
|
|
|
61
|
+
function formatMonitorNotificationLine(line: string): {
|
|
62
|
+
content: string;
|
|
63
|
+
truncated: boolean;
|
|
64
|
+
totalBytes: number;
|
|
65
|
+
outputBytes: number;
|
|
66
|
+
} {
|
|
67
|
+
const truncation = truncateTail(line, {
|
|
68
|
+
maxBytes: MONITOR_NOTIFICATION_LINE_MAX_BYTES,
|
|
69
|
+
maxLines: MONITOR_NOTIFICATION_LINE_MAX_LINES,
|
|
70
|
+
});
|
|
71
|
+
const outputBytes = truncation.outputBytes ?? truncation.totalBytes;
|
|
72
|
+
if (!truncation.truncated) {
|
|
73
|
+
return {
|
|
74
|
+
content: truncation.content,
|
|
75
|
+
truncated: false,
|
|
76
|
+
totalBytes: truncation.totalBytes,
|
|
77
|
+
outputBytes,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const notice = `[Monitor output truncated: showing last ${outputBytes} of ${truncation.totalBytes} bytes]`;
|
|
81
|
+
return {
|
|
82
|
+
content: `${truncation.content}\n${notice}`,
|
|
83
|
+
truncated: true,
|
|
84
|
+
totalBytes: truncation.totalBytes,
|
|
85
|
+
outputBytes,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
58
89
|
export class MonitorTool implements AgentTool<typeof monitorSchema, MonitorToolDetails> {
|
|
59
90
|
readonly name = "monitor";
|
|
60
91
|
readonly label = "Monitor";
|
|
@@ -130,7 +161,8 @@ export class MonitorTool implements AgentTool<typeof monitorSchema, MonitorToolD
|
|
|
130
161
|
if (controller.closed) return;
|
|
131
162
|
const notificationId = `${jobId}:${sequence}`;
|
|
132
163
|
const suffix = count > 0 ? `\n(+${count} earlier lines)` : "";
|
|
133
|
-
const
|
|
164
|
+
const notificationLine = formatMonitorNotificationLine(line);
|
|
165
|
+
const content = `<task-notification>\nMonitor task ${jobId} (${params.kind}: ${params.description}) emitted latest state:\n${notificationLine.content}${suffix}\n</task-notification>`;
|
|
134
166
|
const details = {
|
|
135
167
|
taskId: jobId,
|
|
136
168
|
kind: params.kind,
|
|
@@ -139,6 +171,9 @@ export class MonitorTool implements AgentTool<typeof monitorSchema, MonitorToolD
|
|
|
139
171
|
notificationId,
|
|
140
172
|
sequence,
|
|
141
173
|
coalescedCount: count,
|
|
174
|
+
outputTruncated: notificationLine.truncated,
|
|
175
|
+
outputTotalBytes: notificationLine.totalBytes,
|
|
176
|
+
outputBytes: notificationLine.outputBytes,
|
|
142
177
|
};
|
|
143
178
|
pendingNotifications += 1;
|
|
144
179
|
if (pendingNotifications > MAX_PENDING_MONITOR_NOTIFICATIONS) {
|
package/src/tools/resolve.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@gajae-code/agent-core";
|
|
2
|
+
import type { ToolChoice } from "@gajae-code/ai";
|
|
2
3
|
import type { Component } from "@gajae-code/tui";
|
|
3
4
|
import { Text } from "@gajae-code/tui";
|
|
4
5
|
import { prompt, untilAborted } from "@gajae-code/utils";
|
|
@@ -38,6 +39,21 @@ export interface ResolveToolDetails {
|
|
|
38
39
|
* semantics. No session-level abstraction is needed: callers pass their
|
|
39
40
|
* apply/reject functions directly.
|
|
40
41
|
*/
|
|
42
|
+
/**
|
|
43
|
+
* Tags preview-fallback handlers installed in the session's standing-resolve
|
|
44
|
+
* slot so newer previews can replace older ones (latest-preview-wins) without
|
|
45
|
+
* ever displacing a mode-owned handler such as plan mode's approval handler.
|
|
46
|
+
*/
|
|
47
|
+
const previewResolveFallbacks = new WeakSet<object>();
|
|
48
|
+
|
|
49
|
+
function markPreviewResolveFallback(handler: (input: unknown) => Promise<unknown> | unknown): void {
|
|
50
|
+
previewResolveFallbacks.add(handler);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isPreviewResolveFallback(handler: (input: unknown) => Promise<unknown> | unknown): boolean {
|
|
54
|
+
return previewResolveFallbacks.has(handler);
|
|
55
|
+
}
|
|
56
|
+
|
|
41
57
|
export function queueResolveHandler(
|
|
42
58
|
session: ToolSession,
|
|
43
59
|
options: {
|
|
@@ -48,8 +64,6 @@ export function queueResolveHandler(
|
|
|
48
64
|
},
|
|
49
65
|
): void {
|
|
50
66
|
const queue = session.getToolChoiceQueue?.();
|
|
51
|
-
const forced = session.buildToolChoice?.("resolve");
|
|
52
|
-
if (!queue || !forced || typeof forced === "string") return;
|
|
53
67
|
|
|
54
68
|
const steerReminder = (): void => {
|
|
55
69
|
session.steer?.({
|
|
@@ -63,27 +77,88 @@ export function queueResolveHandler(
|
|
|
63
77
|
});
|
|
64
78
|
};
|
|
65
79
|
|
|
66
|
-
|
|
80
|
+
// Re-evaluated on every push (including apply-error re-pushes) so a runtime
|
|
81
|
+
// incapability discovered mid-turn degrades the NEXT push instead of
|
|
82
|
+
// replaying a stale forced choice the model can never satisfy.
|
|
83
|
+
const resolveForcedChoice = (): { forced: ToolChoice | undefined; exactNamed: boolean } => {
|
|
84
|
+
const toolChoiceResult = session.buildToolChoiceResult?.("resolve");
|
|
85
|
+
if (toolChoiceResult !== undefined) {
|
|
86
|
+
return { forced: toolChoiceResult.choice, exactNamed: toolChoiceResult.exactNamed };
|
|
87
|
+
}
|
|
88
|
+
// Legacy bridge fallback: sessions that only provide buildToolChoice
|
|
89
|
+
// (older SDK embedders, test harnesses) keep the previous behavior — a
|
|
90
|
+
// named object choice is treated as exact named forcing.
|
|
91
|
+
const legacyChoice = session.buildToolChoice?.("resolve");
|
|
92
|
+
const isNamedObject = typeof legacyChoice === "object" && legacyChoice !== null;
|
|
93
|
+
return { forced: isNamedObject ? legacyChoice : undefined, exactNamed: isNamedObject };
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const clearFallback = (): void => {
|
|
97
|
+
// Identity-aware: only clear the shared standing slot when it still holds
|
|
98
|
+
// THIS preview's fallback. Plan mode (or a newer preview) may have
|
|
99
|
+
// replaced it in the meantime — leave theirs untouched.
|
|
100
|
+
if (session.peekStandingResolveHandler?.() === invoke) {
|
|
101
|
+
session.setStandingResolveHandler?.(null);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const invoke = async (input: unknown): Promise<AgentToolResult<unknown>> => {
|
|
106
|
+
const result = await runResolveInvocation(input as ResolveParams, {
|
|
107
|
+
sourceToolName: options.sourceToolName,
|
|
108
|
+
label: options.label,
|
|
109
|
+
apply: options.apply,
|
|
110
|
+
reject: options.reject,
|
|
111
|
+
onApplyError: () => {
|
|
112
|
+
// Apply threw (e.g. ast_edit overlapping replacements). Re-push the
|
|
113
|
+
// same directive so the preview remains pending and the model can
|
|
114
|
+
// `discard` or fix-and-retry on the next turn instead of being
|
|
115
|
+
// stranded with no pending action to address. The re-push goes
|
|
116
|
+
// through the exactNamed gate again — degraded models fall back
|
|
117
|
+
// to the reminder alone. The standing fallback stays installed so
|
|
118
|
+
// a voluntary resolve can still reach the pending action.
|
|
119
|
+
pushDirective();
|
|
120
|
+
steerReminder();
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
// Apply succeeded or the preview was discarded: the pending action is
|
|
124
|
+
// finished, so the voluntary-dispatch fallback must not linger.
|
|
125
|
+
clearFallback();
|
|
126
|
+
return result;
|
|
127
|
+
};
|
|
128
|
+
markPreviewResolveFallback(invoke);
|
|
129
|
+
|
|
130
|
+
// Voluntary-dispatch fallback: when forcing is unavailable (statically
|
|
131
|
+
// degraded) or later removed (runtime degradeInFlight drops the queue
|
|
132
|
+
// directive that owns the invoker), the model can still call `resolve`.
|
|
133
|
+
// ResolveTool.execute consults the queue invoker first, so the standing
|
|
134
|
+
// handler only serves degraded paths. Latest preview wins (mirroring the
|
|
135
|
+
// queue's pushOnce now:true semantics): a newer preview's fallback replaces
|
|
136
|
+
// an older preview's, but NEVER clobbers a non-preview standing handler
|
|
137
|
+
// (e.g. plan mode's approval handler).
|
|
138
|
+
const installFallback = (): void => {
|
|
139
|
+
if (!session.setStandingResolveHandler) return;
|
|
140
|
+
const existing = session.peekStandingResolveHandler?.();
|
|
141
|
+
if (existing === invoke) return;
|
|
142
|
+
if (existing !== undefined && !isPreviewResolveFallback(existing)) return;
|
|
143
|
+
session.setStandingResolveHandler(invoke);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const pushDirective = (): boolean => {
|
|
147
|
+
const { forced, exactNamed } = resolveForcedChoice();
|
|
148
|
+
if (!queue || !forced || !exactNamed) {
|
|
149
|
+
installFallback();
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
67
152
|
queue.pushOnce(forced, {
|
|
68
153
|
label: `pending-action:${options.sourceToolName}`,
|
|
69
154
|
now: true,
|
|
70
155
|
onRejected: () => "requeue",
|
|
71
|
-
onInvoked:
|
|
72
|
-
runResolveInvocation(input as ResolveParams, {
|
|
73
|
-
sourceToolName: options.sourceToolName,
|
|
74
|
-
label: options.label,
|
|
75
|
-
apply: options.apply,
|
|
76
|
-
reject: options.reject,
|
|
77
|
-
onApplyError: () => {
|
|
78
|
-
// Apply threw (e.g. ast_edit overlapping replacements). Re-push the
|
|
79
|
-
// same directive so the preview remains pending and the model can
|
|
80
|
-
// `discard` or fix-and-retry on the next turn instead of being
|
|
81
|
-
// stranded with no pending action to address.
|
|
82
|
-
pushDirective();
|
|
83
|
-
steerReminder();
|
|
84
|
-
},
|
|
85
|
-
}),
|
|
156
|
+
onInvoked: invoke,
|
|
86
157
|
});
|
|
158
|
+
// Forced directive may still be degraded mid-turn by a runtime
|
|
159
|
+
// incapability discovery; keep the fallback armed for that case.
|
|
160
|
+
installFallback();
|
|
161
|
+
return true;
|
|
87
162
|
};
|
|
88
163
|
|
|
89
164
|
pushDirective();
|
|
@@ -66,6 +66,15 @@ function renderSubagentSnapshot(
|
|
|
66
66
|
if (snapshot.agent && snapshot.agent !== "unknown") {
|
|
67
67
|
lines.push(` ${theme.fg("dim", `Agent: ${snapshot.agent} (${snapshot.agentSource})`)}`);
|
|
68
68
|
}
|
|
69
|
+
if (snapshot.effectiveModel) {
|
|
70
|
+
if (snapshot.modelFellBack && snapshot.requestedModel) {
|
|
71
|
+
lines.push(
|
|
72
|
+
` ${theme.fg("warning", `Model: ${snapshot.effectiveModel} (requested ${snapshot.requestedModel}, fell back — no credentials)`)}`,
|
|
73
|
+
);
|
|
74
|
+
} else {
|
|
75
|
+
lines.push(` ${theme.fg("dim", `Model: ${snapshot.effectiveModel}`)}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
69
78
|
if (snapshot.description) lines.push(` ${theme.fg("dim", `Description: ${snapshot.description}`)}`);
|
|
70
79
|
if (snapshot.outputRef) lines.push(` ${theme.fg("dim", `Output: ${snapshot.outputRef}`)}`);
|
|
71
80
|
if (snapshot.assignment) {
|
package/src/tools/subagent.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as path from "node:path";
|
|
|
2
2
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@gajae-code/agent-core";
|
|
3
3
|
import { prompt } from "@gajae-code/utils";
|
|
4
4
|
import * as z from "zod/v4";
|
|
5
|
-
import { type AsyncJob, AsyncJobManager, type SubagentRecord } from "../async";
|
|
5
|
+
import { type AsyncJob, AsyncJobManager, jobElapsedMs, type SubagentRecord } from "../async";
|
|
6
6
|
import subagentDescription from "../prompts/tools/subagent.md" with { type: "text" };
|
|
7
7
|
import type { AgentProgress, AgentSource } from "../task/types";
|
|
8
8
|
import { Ellipsis, truncateToWidth } from "../tui";
|
|
@@ -67,6 +67,12 @@ export interface SubagentSnapshot {
|
|
|
67
67
|
progress?: AgentProgress;
|
|
68
68
|
/** True when a live in-session progress producer exists for this subagent. */
|
|
69
69
|
liveProgressAvailable?: boolean;
|
|
70
|
+
/** Model the subagent actually runs on (after any auth fallback). */
|
|
71
|
+
effectiveModel?: string;
|
|
72
|
+
/** Model originally requested via role/preset mapping; differs from effective on fallback. */
|
|
73
|
+
requestedModel?: string;
|
|
74
|
+
/** True when the requested model lacked credentials and fell back to the parent model. */
|
|
75
|
+
modelFellBack?: boolean;
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
export interface SubagentToolDetails {
|
|
@@ -508,6 +514,13 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
508
514
|
lines.push(`### ${snapshot.id} — ${snapshot.status}`);
|
|
509
515
|
if (snapshot.jobId !== snapshot.id) lines.push(`Job: ${snapshot.jobId}`);
|
|
510
516
|
if (snapshot.agent) lines.push(`Agent: ${snapshot.agent} (${snapshot.agentSource})`);
|
|
517
|
+
if (snapshot.effectiveModel) {
|
|
518
|
+
lines.push(
|
|
519
|
+
snapshot.modelFellBack && snapshot.requestedModel
|
|
520
|
+
? `Model: ${snapshot.effectiveModel} (requested ${snapshot.requestedModel}, fell back — no credentials)`
|
|
521
|
+
: `Model: ${snapshot.effectiveModel}`,
|
|
522
|
+
);
|
|
523
|
+
}
|
|
511
524
|
if (snapshot.description) lines.push(`Description: ${snapshot.description}`);
|
|
512
525
|
if (snapshot.outputRef) lines.push(`Output: ${snapshot.outputRef}`);
|
|
513
526
|
if (snapshot.assignment) lines.push("Assignment:", "```", snapshot.assignment, "```");
|
|
@@ -584,9 +597,19 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
584
597
|
durationMs: 0,
|
|
585
598
|
...(verifiedOutputIds.has(record.subagentId) ? { outputRef: `agent://${record.subagentId}` } : {}),
|
|
586
599
|
...liveFields,
|
|
600
|
+
...this.#modelFields(record),
|
|
587
601
|
};
|
|
588
602
|
}
|
|
589
603
|
|
|
604
|
+
#modelFields(record?: SubagentRecord): Partial<SubagentSnapshot> {
|
|
605
|
+
if (!record) return {};
|
|
606
|
+
const fields: Partial<SubagentSnapshot> = {};
|
|
607
|
+
if (record.effectiveModel) fields.effectiveModel = record.effectiveModel;
|
|
608
|
+
if (record.requestedModel) fields.requestedModel = record.requestedModel;
|
|
609
|
+
if (record.modelFellBack) fields.modelFellBack = true;
|
|
610
|
+
return fields;
|
|
611
|
+
}
|
|
612
|
+
|
|
590
613
|
#snapshot(
|
|
591
614
|
job: AsyncJob,
|
|
592
615
|
timedOut = false,
|
|
@@ -608,7 +631,7 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
608
631
|
label: sanitizeText(job.label, RECEIPT_PREVIEW_WIDTH),
|
|
609
632
|
agent: subagent?.agent ?? "unknown",
|
|
610
633
|
agentSource: subagent?.agentSource ?? "bundled",
|
|
611
|
-
durationMs:
|
|
634
|
+
durationMs: jobElapsedMs(job),
|
|
612
635
|
...(subagent?.description ? { description: sanitizeText(subagent.description, RECEIPT_PREVIEW_WIDTH) } : {}),
|
|
613
636
|
...(verbosity === "full" && subagent?.assignment
|
|
614
637
|
? { assignment: sanitizeText(subagent.assignment, FULL_PREVIEW_WIDTH) }
|
|
@@ -622,6 +645,7 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
622
645
|
: {}),
|
|
623
646
|
...(outputRef ? { outputRef } : {}),
|
|
624
647
|
...(runningTimeoutGuidance ? { guidance: runningTimeoutGuidance } : {}),
|
|
648
|
+
...this.#modelFields(record),
|
|
625
649
|
};
|
|
626
650
|
}
|
|
627
651
|
|
package/src/utils/edit-mode.ts
CHANGED
package/src/utils/tool-choice.ts
CHANGED
|
@@ -1,33 +1,62 @@
|
|
|
1
|
-
import type { Api, Model, ToolChoice } from "@gajae-code/ai";
|
|
1
|
+
import type { Api, Model, ResolveToolChoiceResult, ToolChoice } from "@gajae-code/ai";
|
|
2
|
+
import { resolveToolChoice } from "@gajae-code/ai";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Build a provider-aware tool choice that targets one specific tool when supported.
|
|
5
6
|
* Providers that only expose required/any forcing may still honor named choices by
|
|
6
7
|
* narrowing their request tool list before transport.
|
|
7
8
|
*/
|
|
8
|
-
export
|
|
9
|
-
|
|
9
|
+
export interface NamedToolChoiceResult {
|
|
10
|
+
choice: ToolChoice | undefined;
|
|
11
|
+
exactNamed: boolean;
|
|
12
|
+
resolved?: ResolveToolChoiceResult;
|
|
13
|
+
}
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
15
|
+
export function buildNamedToolChoiceResult(toolName: string, model?: Model<Api>): NamedToolChoiceResult {
|
|
16
|
+
if (!model) return { choice: undefined, exactNamed: false };
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
let namedChoice: ToolChoice | undefined;
|
|
19
|
+
let namedShape = false;
|
|
20
|
+
|
|
21
|
+
if (model.api === "anthropic-messages" || model.api === "bedrock-converse-stream") {
|
|
22
|
+
namedChoice = { type: "tool", name: toolName };
|
|
23
|
+
namedShape = true;
|
|
24
|
+
} else if (
|
|
16
25
|
model.api === "openai-codex-responses" ||
|
|
17
26
|
model.api === "openai-responses" ||
|
|
18
27
|
model.api === "openai-completions" ||
|
|
19
|
-
model.api === "azure-openai-responses"
|
|
28
|
+
model.api === "azure-openai-responses" ||
|
|
29
|
+
model.api === "ollama-chat"
|
|
30
|
+
) {
|
|
31
|
+
namedChoice = { type: "function", name: toolName };
|
|
32
|
+
namedShape = true;
|
|
33
|
+
} else if (
|
|
34
|
+
model.api === "google-generative-ai" ||
|
|
35
|
+
model.api === "google-gemini-cli" ||
|
|
36
|
+
model.api === "google-vertex"
|
|
20
37
|
) {
|
|
21
|
-
|
|
38
|
+
namedChoice = "required";
|
|
22
39
|
}
|
|
23
40
|
|
|
24
|
-
if (
|
|
25
|
-
return { type: "function", name: toolName };
|
|
26
|
-
}
|
|
41
|
+
if (!namedChoice) return { choice: undefined, exactNamed: false };
|
|
27
42
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
43
|
+
const resolved = resolveToolChoice(model, namedChoice);
|
|
44
|
+
const exactNamed = namedShape && resolved.resolvedLevel === "named" && resolved.targetToolName === toolName;
|
|
45
|
+
return {
|
|
46
|
+
choice: exactNamed ? resolved.resolvedChoice : undefined,
|
|
47
|
+
exactNamed,
|
|
48
|
+
resolved,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
31
51
|
|
|
32
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Legacy capability-aware wrapper. May return a lossy `"required"` when named
|
|
54
|
+
* forcing degrades (e.g. Google APIs, or compat `toolChoiceSupport: "required"`),
|
|
55
|
+
* which forces *some* tool rather than `toolName` specifically. Queue directives
|
|
56
|
+
* that need exact tool identity (resolve / todo_write / yield) MUST use
|
|
57
|
+
* `buildNamedToolChoiceResult` and gate on `exactNamed` instead.
|
|
58
|
+
*/
|
|
59
|
+
export function buildNamedToolChoice(toolName: string, model?: Model<Api>): ToolChoice | undefined {
|
|
60
|
+
const result = buildNamedToolChoiceResult(toolName, model);
|
|
61
|
+
return result.choice ?? result.resolved?.resolvedChoice;
|
|
33
62
|
}
|