@gajae-code/coding-agent 0.11.7 → 0.11.9
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 +73 -1
- package/dist/types/cli/args.d.ts +0 -2
- package/dist/types/cli.d.ts +1 -0
- package/dist/types/config/keybindings.d.ts +33 -3
- package/dist/types/config/model-profile-activation.d.ts +4 -3
- package/dist/types/config/model-profile-contract.d.ts +38 -0
- package/dist/types/config/model-profiles.d.ts +2 -3
- package/dist/types/config/settings-schema.d.ts +15 -2
- package/dist/types/edit/modes/patch.d.ts +13 -0
- package/dist/types/edit/path-mutation-lock.d.ts +15 -0
- package/dist/types/eval/py/executor.d.ts +1 -1
- package/dist/types/eval/py/tool-bridge.d.ts +1 -2
- package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +12 -5
- package/dist/types/gjc-runtime/deep-interview-draft.d.ts +8 -0
- package/dist/types/gjc-runtime/deep-interview-payload.d.ts +41 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +32 -17
- package/dist/types/gjc-runtime/deep-interview-repair.d.ts +17 -0
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +135 -4
- package/dist/types/gjc-runtime/repository-binding.d.ts +63 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +44 -0
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +1 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +24 -0
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +5 -3
- package/dist/types/main.d.ts +8 -0
- package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +2 -1
- package/dist/types/modes/components/tool-status-header.d.ts +3 -2
- package/dist/types/modes/components/welcome.d.ts +4 -0
- package/dist/types/modes/controllers/command-controller.d.ts +2 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +5 -2
- package/dist/types/modes/prompt-action-autocomplete.d.ts +6 -1
- package/dist/types/modes/prompt-suggestion-controller.d.ts +35 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +2 -2
- package/dist/types/modes/types.d.ts +3 -0
- package/dist/types/modes/utils/hotkeys-markdown.d.ts +2 -1
- package/dist/types/runtime-mcp/manager.d.ts +1 -1
- package/dist/types/runtime-mcp/oauth-flow.d.ts +0 -5
- package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +14 -0
- package/dist/types/runtime-mcp/redaction.d.ts +2 -0
- package/dist/types/sdk/broker/broker.d.ts +2 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +7 -1
- package/dist/types/sdk/bus/config-commands.d.ts +2 -2
- package/dist/types/sdk/bus/config.d.ts +2 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +1 -0
- package/dist/types/sdk/bus/index.d.ts +25 -0
- package/dist/types/sdk/bus/notification-orchestration.d.ts +1 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +105 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +15 -3
- package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +128 -1
- package/dist/types/sdk/bus/telegram-reference.d.ts +2 -0
- package/dist/types/sdk/host/control/operations.d.ts +1 -1
- package/dist/types/sdk/host/host.d.ts +1 -1
- package/dist/types/sdk/host/query/handlers.d.ts +9 -0
- package/dist/types/sdk/index.d.ts +2 -0
- package/dist/types/sdk/prompt-status.d.ts +88 -0
- package/dist/types/sdk/session-directory.d.ts +1 -1
- package/dist/types/sdk/startup-capability.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/session/internal/managed-session-scope.d.ts +22 -3
- package/dist/types/session/internal/managed-session-storage.d.ts +24 -4
- package/dist/types/session/internal/native-publish-outcome.d.ts +48 -0
- package/dist/types/session/session-manager.d.ts +5 -1
- package/dist/types/session/session-storage.d.ts +31 -0
- package/dist/types/skill-state/active-state.d.ts +4 -0
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/task/receipt.d.ts +2 -0
- package/dist/types/task/types.d.ts +94 -0
- package/dist/types/tools/ask.d.ts +454 -174
- package/dist/types/tools/image-gen.d.ts +47 -1
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/utils/prompt-suggestion.d.ts +33 -0
- package/package.json +8 -8
- package/scripts/capture-platform-shortcut-labels-showcase.ts +342 -0
- package/scripts/dogfood-repository-binding.ts +209 -0
- package/scripts/generate-sdk-operation-inventory.ts +2 -0
- package/scripts/run-test-manifest.ts +4 -4
- package/src/cli/args.ts +2 -92
- package/src/cli/mcp-cli.ts +4 -21
- package/src/cli/notify-cli.ts +5 -38
- package/src/cli.ts +50 -0
- package/src/commands/deep-interview.ts +9 -4
- package/src/commands/sdk.ts +2 -2
- package/src/commit/agentic/index.ts +3 -3
- package/src/commit/map-reduce/index.ts +2 -2
- package/src/config/keybindings.ts +157 -24
- package/src/config/model-profile-activation.ts +7 -18
- package/src/config/model-profile-contract.ts +161 -0
- package/src/config/model-profiles.ts +2 -3
- package/src/config/model-registry.ts +11 -1
- package/src/config/settings-schema.ts +17 -2
- package/src/coordinator-mcp/model-preset.ts +21 -74
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +80 -165
- package/src/defaults/gjc/skills/ralplan/SKILL.md +3 -1
- package/src/edit/index.ts +3 -6
- package/src/edit/modes/patch.ts +38 -1
- package/src/edit/modes/replace.ts +42 -0
- package/src/edit/path-mutation-lock.ts +112 -0
- package/src/eval/py/executor.ts +30 -11
- package/src/eval/py/prelude.py +2 -2
- package/src/eval/py/tool-bridge.ts +34 -18
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +2 -1
- package/src/gjc-runtime/deep-interview-ambiguity.ts +68 -47
- package/src/gjc-runtime/deep-interview-draft.ts +842 -0
- package/src/gjc-runtime/deep-interview-payload.ts +262 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +294 -226
- package/src/gjc-runtime/deep-interview-repair.ts +1674 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +264 -103
- package/src/gjc-runtime/deep-interview-state.ts +916 -4
- package/src/gjc-runtime/launch-tmux.ts +1 -1
- package/src/gjc-runtime/managed-owner-supervisor.ts +83 -4
- package/src/gjc-runtime/ralplan-runtime.ts +78 -4
- package/src/gjc-runtime/repository-binding.ts +267 -0
- package/src/gjc-runtime/session-state-sidecar.ts +25 -9
- package/src/gjc-runtime/state-writer.ts +166 -2
- package/src/gjc-runtime/tmux-owner-isolation.ts +2 -1
- package/src/gjc-runtime/tmux-sessions.ts +45 -6
- package/src/gjc-runtime/ultragoal-guard.ts +5 -5
- package/src/gjc-runtime/ultragoal-runtime.ts +181 -19
- package/src/gjc-runtime/workflow-manifest.generated.json +386 -1
- package/src/gjc-runtime/workflow-manifest.ts +191 -5
- package/src/internal-urls/docs-index.generated.ts +8 -6
- package/src/internal-urls/local-protocol.ts +33 -16
- package/src/internal-urls/mcp-protocol.ts +73 -11
- package/src/main.ts +25 -10
- package/src/modes/DESIGN.md +5 -0
- package/src/modes/acp/acp-agent.ts +50 -16
- package/src/modes/components/model-selector.ts +42 -8
- package/src/modes/components/plan-preview-overlay.ts +1 -1
- package/src/modes/components/runtime-mcp-add-wizard.ts +5 -0
- package/src/modes/components/tool-status-header.ts +20 -4
- package/src/modes/components/welcome.ts +32 -20
- package/src/modes/controllers/command-controller.ts +97 -54
- package/src/modes/controllers/event-controller.ts +10 -0
- package/src/modes/controllers/input-controller.ts +7 -2
- package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -6
- package/src/modes/controllers/selector-controller.ts +46 -54
- package/src/modes/interactive-mode.ts +70 -36
- package/src/modes/prompt-action-autocomplete.ts +32 -13
- package/src/modes/prompt-suggestion-controller.ts +96 -0
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +2 -2
- package/src/modes/types.ts +3 -0
- package/src/modes/utils/hotkeys-markdown.ts +64 -36
- package/src/modes/utils/ui-helpers.ts +5 -3
- package/src/prompts/agents/architect.md +0 -1
- package/src/prompts/agents/critic.md +0 -1
- package/src/prompts/system/prompt-suggestion-system.md +32 -0
- package/src/prompts/system/system-prompt.md +7 -4
- package/src/prompts/tools/cron.md +3 -1
- package/src/runtime-mcp/json-rpc.ts +7 -4
- package/src/runtime-mcp/manager.ts +51 -53
- package/src/runtime-mcp/oauth-flow.ts +0 -72
- package/src/runtime-mcp/plugin-network-boundary.ts +101 -0
- package/src/runtime-mcp/redaction.ts +30 -0
- package/src/runtime-mcp/transports/http.ts +13 -6
- package/src/sdk/broker/broker.ts +34 -20
- package/src/sdk/broker/lifecycle.ts +185 -36
- package/src/sdk/broker/session-index.ts +11 -1
- package/src/sdk/bus/chat-daemon-control.ts +20 -5
- package/src/sdk/bus/config-commands.ts +2 -2
- package/src/sdk/bus/config.ts +9 -1
- package/src/sdk/bus/conversation-store.ts +40 -6
- package/src/sdk/bus/index.ts +240 -65
- package/src/sdk/bus/lifecycle-commands.ts +47 -3
- package/src/sdk/bus/lifecycle-control-runtime.ts +11 -2
- package/src/sdk/bus/lifecycle-orchestrator.ts +32 -1
- package/src/sdk/bus/notification-orchestration.ts +12 -0
- package/src/sdk/bus/notification-service.ts +27 -2
- package/src/sdk/bus/prompt-reconciliation.ts +239 -0
- package/src/sdk/bus/recent-activity.ts +90 -26
- package/src/sdk/bus/telegram-daemon-contract.ts +15 -3
- package/src/sdk/bus/telegram-daemon-control.ts +145 -33
- package/src/sdk/bus/telegram-daemon.ts +853 -112
- package/src/sdk/bus/telegram-reference.ts +20 -2
- package/src/sdk/cli/session-cli.ts +15 -2
- package/src/sdk/host/control/dispatch.ts +1 -1
- package/src/sdk/host/control/operations.ts +1 -1
- package/src/sdk/host/host.ts +2 -6
- package/src/sdk/host/query/handlers.ts +70 -5
- package/src/sdk/index.ts +12 -0
- package/src/sdk/prompt-status.ts +97 -0
- package/src/sdk/protocol/operation-inventory.generated.json +47 -0
- package/src/sdk/protocol/operation-registry.ts +18 -4
- package/src/sdk/session-directory.ts +5 -1
- package/src/sdk/session.ts +2 -0
- package/src/sdk/startup-capability.ts +9 -1
- package/src/session/agent-session.ts +138 -5
- package/src/session/blob-store.ts +7 -2
- package/src/session/internal/managed-session-scope.ts +998 -133
- package/src/session/internal/managed-session-storage.ts +268 -203
- package/src/session/internal/native-publish-outcome.ts +266 -0
- package/src/session/session-manager.ts +406 -360
- package/src/session/session-storage.ts +151 -11
- package/src/skill-state/active-state.ts +10 -0
- package/src/task/executor.ts +13 -5
- package/src/task/gjc-command.ts +2 -2
- package/src/task/index.ts +121 -15
- package/src/task/receipt.ts +3 -0
- package/src/task/types.ts +34 -3
- package/src/tools/ask.ts +199 -88
- package/src/tools/ast-edit.ts +2 -2
- package/src/tools/bash-pty-selection.ts +2 -2
- package/src/tools/browser/screenshot-format.ts +6 -1
- package/src/tools/cron.ts +1 -1
- package/src/tools/image-gen.ts +178 -6
- package/src/tools/index.ts +4 -0
- package/src/tools/render-utils.ts +7 -1
- package/src/utils/edit-mode.ts +2 -2
- package/src/utils/prompt-suggestion.ts +265 -0
- package/src/utils/sixel.ts +3 -3
- package/src/web/insane/url-guard.ts +3 -1
- package/src/web/scrapers/docs-rs.ts +30 -5
package/src/task/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@gajae-code/agent-core";
|
|
2
2
|
import type { Usage } from "@gajae-code/ai";
|
|
3
|
-
import { $
|
|
3
|
+
import { $pickenv } from "@gajae-code/utils";
|
|
4
4
|
import * as z from "zod/v4";
|
|
5
5
|
import { isValidTaskId, TASK_ID_DESCRIPTION } from "./id";
|
|
6
6
|
import type { TaskResultReceipt } from "./receipt";
|
|
@@ -27,10 +27,10 @@ const parseNumber = (value: string | undefined, defaultValue: number): number =>
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
/** Maximum output bytes per agent */
|
|
30
|
-
export const MAX_OUTPUT_BYTES = parseNumber($
|
|
30
|
+
export const MAX_OUTPUT_BYTES = parseNumber($pickenv("GJC_TASK_MAX_OUTPUT_BYTES", "PI_TASK_MAX_OUTPUT_BYTES"), 500_000);
|
|
31
31
|
|
|
32
32
|
/** Maximum output lines per agent */
|
|
33
|
-
export const MAX_OUTPUT_LINES = parseNumber($
|
|
33
|
+
export const MAX_OUTPUT_LINES = parseNumber($pickenv("GJC_TASK_MAX_OUTPUT_LINES", "PI_TASK_MAX_OUTPUT_LINES"), 5000);
|
|
34
34
|
|
|
35
35
|
/** EventBus channel for raw subagent events */
|
|
36
36
|
export const TASK_SUBAGENT_EVENT_CHANNEL = "task:subagent:event";
|
|
@@ -74,6 +74,19 @@ const spawnPlanSchema = z
|
|
|
74
74
|
})
|
|
75
75
|
.describe("justification required before spawning more than four tasks");
|
|
76
76
|
|
|
77
|
+
const repositoryBindingSchema = z
|
|
78
|
+
.object({
|
|
79
|
+
schema: z.literal("gjc.repository_binding.v1"),
|
|
80
|
+
worktreeRoot: z.string().min(1).describe("canonical git worktree root"),
|
|
81
|
+
commonDir: z.string().min(1).nullable().describe("git common dir, or null outside a git checkout"),
|
|
82
|
+
relativeSubdir: z.string().min(1).optional().describe("optional repo-relative subdirectory; not an absolute cwd"),
|
|
83
|
+
displayPath: z.string().min(1).optional().describe("human-facing path; never used for authority"),
|
|
84
|
+
head: z.string().min(1).optional(),
|
|
85
|
+
branch: z.string().min(1).optional(),
|
|
86
|
+
})
|
|
87
|
+
.strict()
|
|
88
|
+
.describe("authoritative repository identity for multi-repo fail-closed spawn");
|
|
89
|
+
|
|
77
90
|
const createTaskItemSchema = (_contextEnabled: boolean) =>
|
|
78
91
|
z.object({
|
|
79
92
|
id: z.string().max(48).refine(isValidTaskId, TASK_ID_DESCRIPTION).describe("filesystem-safe task identifier"),
|
|
@@ -85,6 +98,11 @@ const createTaskItemSchema = (_contextEnabled: boolean) =>
|
|
|
85
98
|
.describe(
|
|
86
99
|
"fork-context mode: none/omitted copies no parent context; receipt copies a minimal receipt-sized snapshot; last-turn copies only the latest exchange; bounded copies the bounded default snapshot; full copies a larger sanitized snapshot up to the configured/model token cap",
|
|
87
100
|
),
|
|
101
|
+
repositoryBinding: repositoryBindingSchema
|
|
102
|
+
.optional()
|
|
103
|
+
.describe(
|
|
104
|
+
"authoritative repository identity; omitted items are stamped from session cwd before discovery/spawn and still fail closed on sibling drift",
|
|
105
|
+
),
|
|
88
106
|
});
|
|
89
107
|
|
|
90
108
|
/** Single task item for parallel execution (default shape with context enabled). */
|
|
@@ -353,6 +371,19 @@ export interface SingleResult {
|
|
|
353
371
|
* never changes the actual mode selection).
|
|
354
372
|
*/
|
|
355
373
|
forkContextAdvisory?: { recommendedMode: ForkContextMode; reasons: string[] };
|
|
374
|
+
/**
|
|
375
|
+
* Resolved repository identity used for this task after pre-discovery stamping
|
|
376
|
+
* and fail-closed validation (#2901).
|
|
377
|
+
*/
|
|
378
|
+
repositoryBinding?: {
|
|
379
|
+
schema: "gjc.repository_binding.v1";
|
|
380
|
+
worktreeRoot: string;
|
|
381
|
+
commonDir: string | null;
|
|
382
|
+
relativeSubdir?: string;
|
|
383
|
+
displayPath?: string;
|
|
384
|
+
head?: string;
|
|
385
|
+
branch?: string;
|
|
386
|
+
};
|
|
356
387
|
}
|
|
357
388
|
|
|
358
389
|
/** True only for complete, factual five-bucket cost accounting. */
|
package/src/tools/ask.ts
CHANGED
|
@@ -160,20 +160,23 @@ const DeepInterviewMetadata = z.object({
|
|
|
160
160
|
.optional(),
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
163
|
+
const DeepInterviewTopologyMeta = DeepInterviewMetadata.extend({
|
|
164
|
+
round: z.literal(0).describe("Round 0 topology confirmation"),
|
|
165
|
+
component: z.literal("review-topology"),
|
|
166
|
+
dimension: z.literal("topology"),
|
|
167
|
+
intent_contract: DeepInterviewIntentContract.describe("required Round 0 locked-intent contract"),
|
|
168
|
+
}).strict();
|
|
169
|
+
|
|
170
|
+
const DeepInterviewRoundMeta = DeepInterviewMetadata.extend({
|
|
171
|
+
round: z.number().int().positive().describe("positive interview round number"),
|
|
172
|
+
}).strict();
|
|
173
|
+
|
|
174
|
+
const DeepInterviewReviewMeta = DeepInterviewMetadata.extend({
|
|
175
|
+
round: z.number().int().positive().describe("positive post-Round-0 review number"),
|
|
176
|
+
intent_review: DeepInterviewIntentReview.describe("post-Round-0 locked-intent reduction review"),
|
|
177
|
+
}).strict();
|
|
178
|
+
|
|
179
|
+
const DeepInterviewMeta = z.union([DeepInterviewTopologyMeta, DeepInterviewRoundMeta, DeepInterviewReviewMeta]);
|
|
177
180
|
type DeepInterviewMeta = z.infer<typeof DeepInterviewMeta>;
|
|
178
181
|
|
|
179
182
|
function intentContract(
|
|
@@ -191,81 +194,118 @@ const WorkflowGateMeta = z.object({
|
|
|
191
194
|
kind: z.enum(["question", "approval", "execution"]).describe("workflow gate kind"),
|
|
192
195
|
});
|
|
193
196
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
message: "intent confirmation options must be unique",
|
|
238
|
-
path: ["deepInterview", "intent_contract"],
|
|
239
|
-
});
|
|
240
|
-
const approvalOptions = review?.approval_options ?? [];
|
|
241
|
-
if (new Set(approvalOptions).size !== approvalOptions.length)
|
|
242
|
-
context.addIssue({
|
|
243
|
-
code: "custom",
|
|
244
|
-
message: "intent approval options must be unique",
|
|
245
|
-
path: ["deepInterview", "intent_review"],
|
|
246
|
-
});
|
|
247
|
-
for (const label of confirmationOptions) {
|
|
248
|
-
if (!labels.has(label))
|
|
197
|
+
function createQuestionItemSchema(deepInterviewSchema: z.ZodType<DeepInterviewMeta>) {
|
|
198
|
+
return z
|
|
199
|
+
.object({
|
|
200
|
+
id: z.string().describe("question id"),
|
|
201
|
+
question: z.string().describe("question text"),
|
|
202
|
+
options: z.array(OptionItem).describe("available options"),
|
|
203
|
+
multi: z.boolean().describe("allow multiple selections").optional(),
|
|
204
|
+
recommended: z.number().describe("recommended option index").optional(),
|
|
205
|
+
deepInterview: deepInterviewSchema.describe("optional deep-interview round metadata").optional(),
|
|
206
|
+
workflowGate: WorkflowGateMeta.describe("optional workflow gate stage/kind override").optional(),
|
|
207
|
+
})
|
|
208
|
+
.superRefine((value, context) => {
|
|
209
|
+
const labels = new Set(value.options.map(option => option.label));
|
|
210
|
+
const contract = intentContract(value.deepInterview);
|
|
211
|
+
const review = intentReview(value.deepInterview);
|
|
212
|
+
if (
|
|
213
|
+
value.deepInterview &&
|
|
214
|
+
value.workflowGate &&
|
|
215
|
+
(value.workflowGate.stage !== "deep-interview" || value.workflowGate.kind !== "question")
|
|
216
|
+
)
|
|
217
|
+
context.addIssue({
|
|
218
|
+
code: "custom",
|
|
219
|
+
message: "deep-interview metadata requires a deep-interview question workflow gate",
|
|
220
|
+
path: ["workflowGate"],
|
|
221
|
+
});
|
|
222
|
+
if (contract && review)
|
|
223
|
+
context.addIssue({
|
|
224
|
+
code: "custom",
|
|
225
|
+
message: "intent contract and review are mutually exclusive",
|
|
226
|
+
path: ["deepInterview"],
|
|
227
|
+
});
|
|
228
|
+
if (
|
|
229
|
+
contract &&
|
|
230
|
+
(value.deepInterview?.round !== 0 ||
|
|
231
|
+
value.deepInterview.component !== "review-topology" ||
|
|
232
|
+
value.deepInterview.dimension !== "topology")
|
|
233
|
+
)
|
|
234
|
+
context.addIssue({
|
|
235
|
+
code: "custom",
|
|
236
|
+
message: "intent contract requires round-0 review topology metadata",
|
|
237
|
+
path: ["deepInterview"],
|
|
238
|
+
});
|
|
239
|
+
if (review && (value.deepInterview?.round ?? 0) <= 0)
|
|
249
240
|
context.addIssue({
|
|
250
241
|
code: "custom",
|
|
251
|
-
message: "intent
|
|
242
|
+
message: "intent review requires a positive round",
|
|
243
|
+
path: ["deepInterview", "round"],
|
|
244
|
+
});
|
|
245
|
+
if ((contract || review) && value.multi === true)
|
|
246
|
+
context.addIssue({ code: "custom", message: "intent gates must be single-select", path: ["multi"] });
|
|
247
|
+
const confirmationOptions = contract?.confirmation_options ?? [];
|
|
248
|
+
if (new Set(confirmationOptions).size !== confirmationOptions.length)
|
|
249
|
+
context.addIssue({
|
|
250
|
+
code: "custom",
|
|
251
|
+
message: "intent confirmation options must be unique",
|
|
252
252
|
path: ["deepInterview", "intent_contract"],
|
|
253
253
|
});
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
if (!labels.has(label))
|
|
254
|
+
const approvalOptions = review?.approval_options ?? [];
|
|
255
|
+
if (new Set(approvalOptions).size !== approvalOptions.length)
|
|
257
256
|
context.addIssue({
|
|
258
257
|
code: "custom",
|
|
259
|
-
message: "intent approval
|
|
258
|
+
message: "intent approval options must be unique",
|
|
260
259
|
path: ["deepInterview", "intent_review"],
|
|
261
260
|
});
|
|
262
|
-
|
|
263
|
-
|
|
261
|
+
for (const label of confirmationOptions) {
|
|
262
|
+
if (!labels.has(label))
|
|
263
|
+
context.addIssue({
|
|
264
|
+
code: "custom",
|
|
265
|
+
message: "intent confirmation option must be displayed",
|
|
266
|
+
path: ["deepInterview", "intent_contract"],
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
for (const label of approvalOptions) {
|
|
270
|
+
if (!labels.has(label))
|
|
271
|
+
context.addIssue({
|
|
272
|
+
code: "custom",
|
|
273
|
+
message: "intent approval option must be displayed",
|
|
274
|
+
path: ["deepInterview", "intent_review"],
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const QuestionItem = createQuestionItemSchema(DeepInterviewMeta);
|
|
281
|
+
const TopologyQuestionItem = createQuestionItemSchema(DeepInterviewTopologyMeta);
|
|
282
|
+
const PostTopologyQuestionItem = createQuestionItemSchema(z.union([DeepInterviewRoundMeta, DeepInterviewReviewMeta]));
|
|
283
|
+
|
|
284
|
+
const OrdinaryQuestionItem = z.object({
|
|
285
|
+
id: z.string().describe("question id"),
|
|
286
|
+
question: z.string().describe("question text"),
|
|
287
|
+
options: z.array(OptionItem).describe("available options"),
|
|
288
|
+
multi: z.boolean().describe("allow multiple selections").optional(),
|
|
289
|
+
recommended: z.number().describe("recommended option index").optional(),
|
|
290
|
+
workflowGate: WorkflowGateMeta.describe("optional workflow gate stage/kind override").optional(),
|
|
291
|
+
});
|
|
264
292
|
|
|
265
293
|
export const askSchema = z.object({
|
|
266
294
|
questions: z.array(QuestionItem).min(1).describe("questions to ask"),
|
|
267
295
|
});
|
|
268
296
|
|
|
297
|
+
const topologyAskSchema = z.object({
|
|
298
|
+
questions: z.array(TopologyQuestionItem).min(1).describe("questions to ask"),
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const postTopologyAskSchema = z.object({
|
|
302
|
+
questions: z.array(PostTopologyQuestionItem).min(1).describe("questions to ask"),
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const ordinaryAskSchema = z.object({
|
|
306
|
+
questions: z.array(OrdinaryQuestionItem).min(1).describe("questions to ask"),
|
|
307
|
+
});
|
|
308
|
+
|
|
269
309
|
export type AskToolInput = z.infer<typeof askSchema>;
|
|
270
310
|
|
|
271
311
|
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -361,9 +401,41 @@ function normalizeRoundZeroOptionalNulls(arguments_: Record<string, unknown>): R
|
|
|
361
401
|
if (changed) normalizedQuestion.deepInterview = normalizedDeepInterview;
|
|
362
402
|
return changed ? { ...arguments_, questions: [normalizedQuestion] } : arguments_;
|
|
363
403
|
}
|
|
364
|
-
|
|
404
|
+
|
|
405
|
+
function knownIntentRejection(arguments_: Record<string, unknown>): RawArgumentValidationResult | undefined {
|
|
406
|
+
if (!isPlainRecord(arguments_) || !Array.isArray(arguments_.questions) || arguments_.questions.length !== 1)
|
|
407
|
+
return undefined;
|
|
408
|
+
const question = arguments_.questions[0];
|
|
409
|
+
if (!isPlainRecord(question) || !isPlainRecord(question.deepInterview)) return undefined;
|
|
410
|
+
const metadata = question.deepInterview;
|
|
411
|
+
const hasContract = Object.hasOwn(metadata, "intent_contract");
|
|
412
|
+
const hasReview = Object.hasOwn(metadata, "intent_review");
|
|
413
|
+
const workflowGate = question.workflowGate;
|
|
414
|
+
if (
|
|
415
|
+
Object.hasOwn(question, "workflowGate") &&
|
|
416
|
+
(!isPlainRecord(workflowGate) || workflowGate.stage !== "deep-interview" || workflowGate.kind !== "question")
|
|
417
|
+
)
|
|
418
|
+
return { outcome: "reject", code: "ask-deep-interview-metadata-requires-deep-interview-gate" };
|
|
419
|
+
if (hasReview && !hasContract && metadata.round === 0) {
|
|
420
|
+
return { outcome: "reject", code: "ask-intent-review-requires-positive-round" };
|
|
421
|
+
}
|
|
422
|
+
if (!hasContract || !isPlainRecord(metadata.intent_contract)) return undefined;
|
|
423
|
+
const contract = metadata.intent_contract;
|
|
424
|
+
if (
|
|
425
|
+
(Array.isArray(contract.items) && contract.items.length === 0) ||
|
|
426
|
+
(Array.isArray(contract.confirmation_options) && contract.confirmation_options.length === 0)
|
|
427
|
+
)
|
|
428
|
+
return { outcome: "reject", code: "ask-intent-contract-requires-non-empty-authority" };
|
|
429
|
+
return undefined;
|
|
430
|
+
}
|
|
431
|
+
function recoverRoundZeroIntentContract(
|
|
432
|
+
arguments_: Record<string, unknown>,
|
|
433
|
+
stage?: "topology" | "post-topology",
|
|
434
|
+
): RawArgumentValidationResult {
|
|
365
435
|
if (!isRoundZeroRecoveryCandidate(arguments_)) return { outcome: "passthrough" };
|
|
366
436
|
const normalizedArguments = normalizeRoundZeroOptionalNulls(arguments_);
|
|
437
|
+
const knownRejection = knownIntentRejection(normalizedArguments);
|
|
438
|
+
if (knownRejection) return knownRejection;
|
|
367
439
|
if (!isOnlyPlainData(normalizedArguments) || !isPlainRecord(normalizedArguments)) return { outcome: "reject" };
|
|
368
440
|
if (
|
|
369
441
|
!hasExactOwnKeys(normalizedArguments, ["questions"]) ||
|
|
@@ -389,6 +461,7 @@ function recoverRoundZeroIntentContract(arguments_: Record<string, unknown>): Ra
|
|
|
389
461
|
const deepInterview = question.deepInterview;
|
|
390
462
|
const hasIntentContract = Object.hasOwn(deepInterview, "intent_contract");
|
|
391
463
|
const hasIntentReview = Object.hasOwn(deepInterview, "intent_review");
|
|
464
|
+
if (hasIntentContract && hasIntentReview && stage !== "topology") return { outcome: "reject" };
|
|
392
465
|
if (hasIntentContract !== hasIntentReview && askSchema.safeParse(normalizedArguments).success)
|
|
393
466
|
return { outcome: "passthrough" };
|
|
394
467
|
|
|
@@ -421,10 +494,19 @@ function recoverRoundZeroIntentContract(arguments_: Record<string, unknown>): Ra
|
|
|
421
494
|
"intent_contract",
|
|
422
495
|
"intent_review",
|
|
423
496
|
];
|
|
497
|
+
if (!hasOnlyAllowedOwnKeys(deepInterview, deepInterviewKeys)) return { outcome: "reject" };
|
|
498
|
+
if (
|
|
499
|
+
stage === "post-topology" &&
|
|
500
|
+
!hasIntentContract &&
|
|
501
|
+
hasIntentReview &&
|
|
502
|
+
typeof deepInterview.round === "number" &&
|
|
503
|
+
Number.isInteger(deepInterview.round) &&
|
|
504
|
+
deepInterview.round > 0
|
|
505
|
+
)
|
|
506
|
+
return { outcome: "passthrough" };
|
|
424
507
|
if (
|
|
425
|
-
!
|
|
426
|
-
!
|
|
427
|
-
!Object.hasOwn(deepInterview, "intent_review") ||
|
|
508
|
+
!hasIntentContract ||
|
|
509
|
+
!hasIntentReview ||
|
|
428
510
|
deepInterview.round !== 0 ||
|
|
429
511
|
typeof deepInterview.component !== "string" ||
|
|
430
512
|
deepInterview.component !== "review-topology" ||
|
|
@@ -522,6 +604,16 @@ function getDoneOptionLabel(): string {
|
|
|
522
604
|
return `${theme.status.success} Done selecting`;
|
|
523
605
|
}
|
|
524
606
|
|
|
607
|
+
function validRecommendedIndex(recommended: number | undefined, optionCount: number): number | undefined {
|
|
608
|
+
return typeof recommended === "number" &&
|
|
609
|
+
Number.isFinite(recommended) &&
|
|
610
|
+
Number.isInteger(recommended) &&
|
|
611
|
+
recommended >= 0 &&
|
|
612
|
+
recommended < optionCount
|
|
613
|
+
? recommended
|
|
614
|
+
: undefined;
|
|
615
|
+
}
|
|
616
|
+
|
|
525
617
|
/** Add "(Recommended)" suffix to the option at the given index if not already present */
|
|
526
618
|
function addRecommendedSuffix(labels: string[], recommendedIndex?: number): string[] {
|
|
527
619
|
if (recommendedIndex === undefined || recommendedIndex < 0 || recommendedIndex >= labels.length) {
|
|
@@ -1050,6 +1142,11 @@ function formatQuestionResult(result: QuestionResult): string {
|
|
|
1050
1142
|
// =============================================================================
|
|
1051
1143
|
|
|
1052
1144
|
type AskParams = AskToolInput;
|
|
1145
|
+
type AskParametersSchema =
|
|
1146
|
+
| typeof ordinaryAskSchema
|
|
1147
|
+
| typeof askSchema
|
|
1148
|
+
| typeof topologyAskSchema
|
|
1149
|
+
| typeof postTopologyAskSchema;
|
|
1053
1150
|
|
|
1054
1151
|
/**
|
|
1055
1152
|
* Ask tool for interactive user prompting during execution.
|
|
@@ -1057,13 +1154,19 @@ type AskParams = AskToolInput;
|
|
|
1057
1154
|
* Allows gathering user preferences, clarifying instructions, and getting decisions
|
|
1058
1155
|
* on implementation choices as the agent works.
|
|
1059
1156
|
*/
|
|
1060
|
-
export class AskTool implements AgentTool<
|
|
1157
|
+
export class AskTool implements AgentTool<AskParametersSchema, AskToolDetails> {
|
|
1061
1158
|
readonly name = "ask";
|
|
1062
1159
|
readonly label = "Ask";
|
|
1063
1160
|
readonly summary = "Ask the user a clarifying question";
|
|
1064
1161
|
readonly description: string;
|
|
1065
|
-
|
|
1066
|
-
|
|
1162
|
+
get parameters(): AskParametersSchema {
|
|
1163
|
+
const stage = this.session.getDeepInterviewAskStage?.();
|
|
1164
|
+
if (stage === "topology") return topologyAskSchema;
|
|
1165
|
+
if (stage === "post-topology") return postTopologyAskSchema;
|
|
1166
|
+
return ordinaryAskSchema;
|
|
1167
|
+
}
|
|
1168
|
+
readonly rawArgumentValidation = (arguments_: Record<string, unknown>): RawArgumentValidationResult =>
|
|
1169
|
+
recoverRoundZeroIntentContract(arguments_, this.session.getDeepInterviewAskStage?.());
|
|
1067
1170
|
readonly strict = true;
|
|
1068
1171
|
readonly loadMode = "discoverable";
|
|
1069
1172
|
|
|
@@ -1098,6 +1201,8 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
1098
1201
|
if (customInput !== undefined && (meta || isDeepInterviewAskQuestion(q.question)))
|
|
1099
1202
|
assertDeepInterviewInputWithinLimit(customInput, MAX_USER_RESPONSE_LENGTH, "user_response");
|
|
1100
1203
|
if (!meta) return;
|
|
1204
|
+
if (this.session.getDeepInterviewAskStage?.() === undefined) return;
|
|
1205
|
+
if (q.workflowGate && (q.workflowGate.stage !== "deep-interview" || q.workflowGate.kind !== "question")) return;
|
|
1101
1206
|
const cwd = this.session.cwd;
|
|
1102
1207
|
const sessionId = this.session.getSessionId?.() ?? undefined;
|
|
1103
1208
|
const statePath = deepInterviewStatePath(cwd, sessionId);
|
|
@@ -1154,12 +1259,13 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
1154
1259
|
};
|
|
1155
1260
|
const gateEmitter = this.session.getWorkflowGateEmitter?.();
|
|
1156
1261
|
// A durable workflow-gate emitter now exists for every session, and its
|
|
1157
|
-
//
|
|
1158
|
-
// The workflow gate is only the headless (non-TUI) answer
|
|
1159
|
-
// interactive UI is present, prefer it — otherwise
|
|
1160
|
-
// route to emitGate() and hang forever waiting on
|
|
1262
|
+
// supportsRemoteGateAnswers() is always true, so it can no longer signal
|
|
1263
|
+
// "no local UI". The workflow gate is only the headless (non-TUI) answer
|
|
1264
|
+
// path: when a real interactive UI is present, prefer it — otherwise
|
|
1265
|
+
// attended TUI asks would route to emitGate() and hang forever waiting on
|
|
1266
|
+
// a remote responder.
|
|
1161
1267
|
const hasInteractiveUi = context?.hasUI === true && !!context.ui;
|
|
1162
|
-
const canUseWorkflowGate = !hasInteractiveUi && gateEmitter?.
|
|
1268
|
+
const canUseWorkflowGate = !hasInteractiveUi && gateEmitter?.supportsRemoteGateAnswers() === true;
|
|
1163
1269
|
// Headless fallback: SDK workflow gates are the non-TUI answer path.
|
|
1164
1270
|
if (!canUseWorkflowGate && (!context?.hasUI || !context.ui)) {
|
|
1165
1271
|
context?.abort();
|
|
@@ -1428,10 +1534,12 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
1428
1534
|
}),
|
|
1429
1535
|
}
|
|
1430
1536
|
: options?.previous;
|
|
1537
|
+
const recommendedIndex = validRecommendedIndex(q.recommended, rawOptionLabels.length);
|
|
1431
1538
|
activeRemoteRequest = {
|
|
1432
1539
|
question: displayQuestion,
|
|
1433
1540
|
options: remoteSelectorOptions,
|
|
1434
1541
|
interaction: "selector",
|
|
1542
|
+
...(recommendedIndex === undefined ? {} : { recommendedIndex }),
|
|
1435
1543
|
controls: askRemoteControls({
|
|
1436
1544
|
multi: q.multi === true,
|
|
1437
1545
|
questionIndex,
|
|
@@ -1463,6 +1571,9 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
1463
1571
|
question: displayQuestion,
|
|
1464
1572
|
options: state.interaction === "selector" ? remoteSelectorOptions : [],
|
|
1465
1573
|
interaction: state.interaction,
|
|
1574
|
+
...(state.interaction === "selector" && recommendedIndex !== undefined
|
|
1575
|
+
? { recommendedIndex }
|
|
1576
|
+
: {}),
|
|
1466
1577
|
controls:
|
|
1467
1578
|
state.interaction === "selector"
|
|
1468
1579
|
? askRemoteControls({
|
package/src/tools/ast-edit.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
|
|
3
3
|
import { type AstReplaceChange, type AstReplaceFileChange, astEdit } from "@gajae-code/natives";
|
|
4
4
|
import type { Component } from "@gajae-code/tui";
|
|
5
5
|
import { Text } from "@gajae-code/tui";
|
|
6
|
-
import { $
|
|
6
|
+
import { $pickenvpos, prompt, untilAborted } from "@gajae-code/utils";
|
|
7
7
|
import * as z from "zod/v4";
|
|
8
8
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
9
9
|
import { computeLineHash, HL_BODY_SEP } from "../hashline/hash";
|
|
@@ -199,7 +199,7 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
|
|
199
199
|
seenPatterns.add(pat);
|
|
200
200
|
}
|
|
201
201
|
const normalizedRewrites = Object.fromEntries(ops);
|
|
202
|
-
const maxFiles = $
|
|
202
|
+
const maxFiles = $pickenvpos(["GJC_MAX_AST_FILES", "PI_MAX_AST_FILES"], 1000);
|
|
203
203
|
|
|
204
204
|
const scope = await resolveToolSearchScope({
|
|
205
205
|
rawPaths: params.paths,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $
|
|
1
|
+
import { $pickenv } from "@gajae-code/utils/env";
|
|
2
2
|
|
|
3
3
|
/** Minimal UI-capability fields needed to decide whether bash can use the local PTY overlay. */
|
|
4
4
|
export interface BashPtyContext {
|
|
@@ -9,6 +9,6 @@ export interface BashPtyContext {
|
|
|
9
9
|
/** Return whether a bash tool call should use the local interactive PTY overlay. */
|
|
10
10
|
export function canUseInteractiveBashPty(pty: boolean, ctx: BashPtyContext | undefined): boolean {
|
|
11
11
|
if (!pty) return false;
|
|
12
|
-
if ($
|
|
12
|
+
if ($pickenv("GJC_NO_PTY", "PI_NO_PTY") === "1") return false;
|
|
13
13
|
return ctx?.hasUI === true && ctx.ui !== undefined;
|
|
14
14
|
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import * as os from "node:os";
|
|
2
|
+
import * as path from "node:path";
|
|
2
3
|
|
|
3
4
|
import { formatDimensionNote, type ResizedImage } from "../../utils/image-resize";
|
|
4
5
|
|
|
5
6
|
function shortenPath(filePath: string): string {
|
|
6
7
|
const home = os.homedir();
|
|
7
|
-
|
|
8
|
+
if (!home) return filePath;
|
|
9
|
+
// Require a separator boundary so a sibling that shares the home prefix
|
|
10
|
+
// (e.g. "/home/woodyx/x" for home "/home/woody") is not corrupted into "~x/x".
|
|
11
|
+
const boundary = home.endsWith(path.sep) ? home : home + path.sep;
|
|
12
|
+
return filePath === home || filePath.startsWith(boundary) ? `~${filePath.slice(home.length)}` : filePath;
|
|
8
13
|
}
|
|
9
14
|
|
|
10
15
|
export function formatScreenshot(opts: {
|
package/src/tools/cron.ts
CHANGED
|
@@ -36,7 +36,7 @@ const cronSchema = z
|
|
|
36
36
|
.string()
|
|
37
37
|
.optional()
|
|
38
38
|
.describe(
|
|
39
|
-
"(op=create, required) Prompt to inject between turns when the cron fires.
|
|
39
|
+
"(op=create, required) Prompt to inject between turns when the cron fires. Every firing starts a normal agent turn whose response may be visible; use a persistent monitor, not cron, for ongoing polling that should emit only on state changes.",
|
|
40
40
|
),
|
|
41
41
|
recurring: z
|
|
42
42
|
.boolean()
|