@gajae-code/coding-agent 0.11.3 → 0.11.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/dist/types/cli/notify-cli.d.ts +5 -1
- package/dist/types/cli/worktree-cli.d.ts +8 -25
- package/dist/types/commands/gc.d.ts +4 -0
- package/dist/types/commands/worktree.d.ts +7 -32
- package/dist/types/config/settings-schema.d.ts +20 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +28 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +9 -0
- package/dist/types/gjc-runtime/launch-tmux.d.ts +3 -0
- package/dist/types/gjc-runtime/managed-owner-admission.d.ts +42 -0
- package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +41 -0
- package/dist/types/gjc-runtime/state-argv.d.ts +38 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +9 -0
- package/dist/types/gjc-runtime/ultragoal-owner-loss-recovery.d.ts +94 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
- package/dist/types/harness-control-plane/owner.d.ts +3 -1
- package/dist/types/internal-urls/local-protocol.d.ts +18 -0
- package/dist/types/main.d.ts +2 -2
- package/dist/types/modes/components/ansi-display-validator.d.ts +2 -0
- package/dist/types/modes/components/custom-editor.d.ts +8 -3
- package/dist/types/modes/components/notifications-settings-editor.d.ts +4 -0
- package/dist/types/modes/components/session-observer-overlay.d.ts +3 -1
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/tool-transcript-format.d.ts +77 -0
- package/dist/types/modes/components/transcript-viewer-overlay.d.ts +3 -0
- package/dist/types/modes/controllers/goal-mode-controller.d.ts +8 -1
- package/dist/types/modes/controllers/input-controller.d.ts +8 -2
- package/dist/types/modes/controllers/selector-controller.d.ts +15 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +3 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +16 -0
- package/dist/types/plan-mode/approved-plan.d.ts +1 -0
- package/dist/types/runtime-mcp/manager.d.ts +2 -0
- package/dist/types/runtime-mcp/oauth-discovery.d.ts +12 -1
- package/dist/types/sdk/broker/broker.d.ts +1 -0
- package/dist/types/sdk/broker/discovery.d.ts +10 -0
- package/dist/types/sdk/broker/ensure.d.ts +24 -1
- package/dist/types/sdk/broker/internal.d.ts +3 -0
- package/dist/types/sdk/broker/session-index.d.ts +12 -0
- package/dist/types/sdk/broker/state-version.d.ts +2 -1
- package/dist/types/sdk/bus/chat-daemon-cli.d.ts +3 -1
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +39 -4
- package/dist/types/sdk/bus/config-commands.d.ts +8 -0
- package/dist/types/sdk/bus/config.d.ts +22 -0
- package/dist/types/sdk/bus/index.d.ts +162 -2
- package/dist/types/sdk/bus/notification-service.d.ts +71 -8
- package/dist/types/sdk/bus/session-control.d.ts +11 -8
- package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +5 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -3
- package/dist/types/sdk/bus/telegram-daemon-control.d.ts +23 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +214 -20
- package/dist/types/sdk/session.d.ts +16 -1
- package/dist/types/secrets/index.d.ts +4 -2
- package/dist/types/secrets/obfuscator.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +31 -2
- package/dist/types/session/artifacts.d.ts +30 -8
- package/dist/types/session/internal/managed-session-scope.d.ts +7 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +63 -6
- package/dist/types/session/session-manager.d.ts +88 -18
- package/dist/types/session/session-storage.d.ts +27 -0
- package/dist/types/session/streaming-output.d.ts +4 -0
- package/dist/types/task/executor.d.ts +9 -0
- package/dist/types/tool-discovery/tool-index.d.ts +2 -0
- package/dist/types/tools/ask.d.ts +87 -12
- package/dist/types/tools/bash-skill-urls.d.ts +2 -2
- package/dist/types/tools/browser/screenshot-format.d.ts +8 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +11 -0
- package/dist/types/tools/browser/tab-supervisor.d.ts +1 -1
- package/dist/types/tools/browser/tab-worker-path-resolver.d.ts +2 -0
- package/dist/types/tools/browser/tab-worker-smoke.d.ts +16 -0
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/tools/path-utils.d.ts +9 -14
- package/dist/types/tools/render-utils.d.ts +1 -8
- package/dist/types/tools/resource-gc.d.ts +14 -0
- package/dist/types/utils/image-loading.d.ts +23 -0
- package/dist/types/utils/image-resize.d.ts +2 -0
- package/dist/types/utils/pasted-image-loading.d.ts +39 -0
- package/dist/types/utils/pasted-image-path.d.ts +21 -19
- package/dist/types/web/scrapers/html-to-markdown.d.ts +5 -0
- package/dist/types/web/scrapers/types.d.ts +1 -5
- package/package.json +25 -9
- package/scripts/generate-sdk-operation-inventory.ts +2 -0
- package/scripts/run-threat-model-check.ts +60 -0
- package/src/cli/notify-cli.ts +40 -18
- package/src/cli/worktree-cli.ts +9 -287
- package/src/cli.ts +10 -9
- package/src/commands/gc.ts +11 -1
- package/src/commands/sdk.ts +10 -4
- package/src/commands/worktree.ts +10 -53
- package/src/config/model-profiles.ts +15 -15
- package/src/config/settings-schema.ts +17 -0
- package/src/daemon/runtime.ts +2 -2
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +70 -49
- package/src/defaults/gjc/skills/ralplan/SKILL.md +0 -6
- package/src/defaults/gjc/skills/team/SKILL.md +7 -9
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +0 -2
- package/src/eval/js/shared/runtime.ts +1 -1
- package/src/eval/py/index.ts +2 -2
- package/src/export/html/template.generated.ts +1 -1
- package/src/extensibility/plugins/marketplace/manager.ts +94 -10
- package/src/gjc-runtime/deep-interview-recorder.ts +7 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +10 -0
- package/src/gjc-runtime/deep-interview-state.ts +127 -3
- package/src/gjc-runtime/gc-render.ts +19 -1
- package/src/gjc-runtime/gc-runtime.ts +68 -3
- package/src/gjc-runtime/launch-tmux.ts +61 -6
- package/src/gjc-runtime/managed-owner-admission.ts +352 -0
- package/src/gjc-runtime/managed-owner-supervisor.ts +162 -0
- package/src/gjc-runtime/state-argv.ts +292 -0
- package/src/gjc-runtime/state-runtime.ts +80 -222
- package/src/gjc-runtime/team-runtime.ts +1 -1
- package/src/gjc-runtime/tmux-owner-isolation.ts +117 -0
- package/src/gjc-runtime/ultragoal-owner-loss-recovery.ts +328 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +14 -0
- package/src/harness-control-plane/owner.ts +59 -3
- package/src/hindsight/client.ts +65 -2
- package/src/internal-urls/agent-protocol.ts +76 -5
- package/src/internal-urls/docs-index.generated.ts +7 -6
- package/src/internal-urls/local-protocol.ts +416 -18
- package/src/lsp/index.ts +4 -88
- package/src/main.ts +37 -13
- package/src/modes/acp/acp-agent.ts +8 -0
- package/src/modes/components/ansi-display-validator.ts +54 -0
- package/src/modes/components/custom-editor.ts +103 -32
- package/src/modes/components/custom-provider-wizard.ts +26 -1
- package/src/modes/components/notifications-settings-editor.ts +26 -2
- package/src/modes/components/session-observer-overlay.ts +40 -37
- package/src/modes/components/settings-selector.ts +3 -1
- package/src/modes/components/tool-transcript-format.ts +393 -0
- package/src/modes/components/transcript-viewer-overlay.ts +63 -1
- package/src/modes/controllers/goal-mode-controller.ts +20 -1
- package/src/modes/controllers/input-controller.ts +98 -37
- package/src/modes/controllers/plan-mode-controller.ts +2 -0
- package/src/modes/controllers/runtime-mcp-command-controller.ts +3 -0
- package/src/modes/controllers/selector-controller.ts +71 -9
- package/src/modes/interactive-mode.ts +14 -2
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -1
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +17 -1
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +4 -2
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +120 -3
- package/src/plan-mode/approved-plan.ts +3 -1
- package/src/prompts/agents/architect.md +1 -0
- package/src/prompts/agents/critic.md +1 -0
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/system-prompt.md +2 -1
- package/src/prompts/tools/lsp.md +3 -3
- package/src/runtime-mcp/manager.ts +16 -1
- package/src/runtime-mcp/oauth-discovery.ts +116 -26
- package/src/sdk/broker/broker.ts +139 -32
- package/src/sdk/broker/discovery.ts +125 -0
- package/src/sdk/broker/ensure.ts +103 -19
- package/src/sdk/broker/internal.ts +10 -0
- package/src/sdk/broker/session-index.ts +309 -38
- package/src/sdk/broker/state-version.ts +5 -2
- package/src/sdk/bus/chat-daemon-cli.ts +72 -10
- package/src/sdk/bus/chat-daemon-control.ts +526 -130
- package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
- package/src/sdk/bus/config-commands.ts +22 -0
- package/src/sdk/bus/config.ts +37 -0
- package/src/sdk/bus/index.ts +384 -92
- package/src/sdk/bus/lifecycle-control-runtime.ts +53 -3
- package/src/sdk/bus/notification-service.ts +284 -62
- package/src/sdk/bus/recent-activity.ts +14 -5
- package/src/sdk/bus/session-control.ts +150 -44
- package/src/sdk/bus/telegram-daemon-cli.ts +20 -14
- package/src/sdk/bus/telegram-daemon-contract.ts +6 -3
- package/src/sdk/bus/telegram-daemon-control.ts +294 -76
- package/src/sdk/bus/telegram-daemon.ts +1842 -281
- package/src/sdk/protocol/operation-inventory.generated.json +11 -0
- package/src/sdk/session.ts +155 -27
- package/src/secrets/index.ts +53 -10
- package/src/secrets/obfuscator.ts +21 -18
- package/src/secrets/regex.ts +2 -0
- package/src/session/agent-session.ts +495 -253
- package/src/session/artifacts.ts +188 -23
- package/src/session/context-estimation.ts +7 -2
- package/src/session/internal/managed-session-scope.ts +215 -39
- package/src/session/internal/managed-session-storage.ts +1152 -46
- package/src/session/session-manager.ts +1055 -148
- package/src/session/session-storage.ts +109 -11
- package/src/session/streaming-output.ts +6 -0
- package/src/task/executor.ts +71 -26
- package/src/task/index.ts +63 -20
- package/src/tool-discovery/tool-index.ts +18 -0
- package/src/tools/ask.ts +329 -57
- package/src/tools/bash-allowed-prefixes.ts +79 -29
- package/src/tools/bash-skill-urls.ts +14 -3
- package/src/tools/bash.ts +2 -2
- package/src/tools/browser/launch.ts +3 -1
- package/src/tools/browser/readable.ts +1 -1
- package/src/tools/browser/screenshot-format.ts +34 -0
- package/src/tools/browser/tab-protocol.ts +3 -0
- package/src/tools/browser/tab-supervisor.ts +125 -122
- package/src/tools/browser/tab-worker-entry.ts +33 -14
- package/src/tools/browser/tab-worker-path-resolver.ts +67 -0
- package/src/tools/browser/tab-worker-smoke.ts +87 -0
- package/src/tools/browser/tab-worker.ts +7 -5
- package/src/tools/index.ts +4 -0
- package/src/tools/path-utils.ts +42 -35
- package/src/tools/plan-mode-guard.ts +1 -0
- package/src/tools/read.ts +7 -1
- package/src/tools/render-utils.ts +1 -27
- package/src/tools/resource-gc.ts +123 -34
- package/src/tools/sqlite-reader.ts +3 -2
- package/src/tools/subagent.ts +2 -0
- package/src/utils/image-loading.ts +85 -34
- package/src/utils/image-resize.ts +16 -9
- package/src/utils/pasted-image-loading.ts +514 -0
- package/src/utils/pasted-image-path.ts +223 -98
- package/src/web/scrapers/html-to-markdown.ts +68 -0
- package/src/web/scrapers/readthedocs.ts +1 -1
- package/src/web/scrapers/types.ts +1 -68
- package/src/web/search/providers/openai-compatible.ts +76 -23
package/src/tools/ask.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@gajae-code/agent-core";
|
|
19
|
+
import type { RawArgumentValidationResult } from "@gajae-code/ai/types";
|
|
19
20
|
import {
|
|
20
21
|
type Component,
|
|
21
22
|
Container,
|
|
@@ -36,11 +37,18 @@ import {
|
|
|
36
37
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
37
38
|
import { appendOrMergeDeepInterviewRound, syncDeepInterviewRecorderHud } from "../gjc-runtime/deep-interview-recorder";
|
|
38
39
|
import { deepInterviewStatePath } from "../gjc-runtime/deep-interview-runtime";
|
|
40
|
+
import {
|
|
41
|
+
assertDeepInterviewInputWithinLimit,
|
|
42
|
+
assertDeepInterviewStructuredResponseWithinLimit,
|
|
43
|
+
deepInterviewCharacterCount,
|
|
44
|
+
MAX_USER_RESPONSE_LENGTH,
|
|
45
|
+
} from "../gjc-runtime/deep-interview-state";
|
|
39
46
|
import {
|
|
40
47
|
type AskGateQuestion,
|
|
41
48
|
gateAnswerToResult,
|
|
42
49
|
questionToGate,
|
|
43
50
|
} from "../modes/shared/agent-wire/deep-interview-gate";
|
|
51
|
+
|
|
44
52
|
import { getMarkdownTheme, type Theme, theme } from "../modes/theme/theme";
|
|
45
53
|
import askDescription from "../prompts/tools/ask.md" with { type: "text" };
|
|
46
54
|
import { renderStatusLine } from "../tui";
|
|
@@ -62,15 +70,35 @@ import { assertUltragoalAskAllowed } from "./ultragoal-ask-guard";
|
|
|
62
70
|
// Types
|
|
63
71
|
// =============================================================================
|
|
64
72
|
|
|
73
|
+
function deepInterviewBoundedString(maximum: number) {
|
|
74
|
+
return z.string().superRefine((value, context) => {
|
|
75
|
+
if (deepInterviewCharacterCount(value) > maximum)
|
|
76
|
+
context.addIssue({
|
|
77
|
+
code: "too_big",
|
|
78
|
+
maximum,
|
|
79
|
+
inclusive: true,
|
|
80
|
+
origin: "string",
|
|
81
|
+
message: `Too big: expected string to have <=${maximum} characters`,
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
65
86
|
const OptionItem = z.object({
|
|
66
87
|
label: z.string().describe("display label"),
|
|
67
88
|
});
|
|
68
89
|
|
|
90
|
+
const DEEP_INTERVIEW_INTENT_ID_PATTERN = /^(artifact|surface|integration|constraint):[a-z0-9][a-z0-9._/-]{0,127}$/;
|
|
91
|
+
|
|
92
|
+
const DeepInterviewReferenceId = z.string().superRefine((value, context) => {
|
|
93
|
+
if (!DEEP_INTERVIEW_INTENT_ID_PATTERN.test(value))
|
|
94
|
+
context.addIssue({ code: "custom", message: "invalid deep-interview intent ID" });
|
|
95
|
+
});
|
|
96
|
+
|
|
69
97
|
const DeepInterviewIntentItem = z
|
|
70
98
|
.object({
|
|
71
|
-
id: z.string().regex(
|
|
99
|
+
id: z.string().regex(DEEP_INTERVIEW_INTENT_ID_PATTERN),
|
|
72
100
|
category: z.enum(["artifact", "surface", "integration", "constraint"]),
|
|
73
|
-
statement:
|
|
101
|
+
statement: deepInterviewBoundedString(1_000).min(1),
|
|
74
102
|
})
|
|
75
103
|
.strict()
|
|
76
104
|
.superRefine((value, context) => {
|
|
@@ -81,7 +109,7 @@ const DeepInterviewIntentItem = z
|
|
|
81
109
|
const DeepInterviewIntentContract = z
|
|
82
110
|
.object({
|
|
83
111
|
items: z.array(DeepInterviewIntentItem).min(1).max(64),
|
|
84
|
-
confirmation_options: z.array(
|
|
112
|
+
confirmation_options: z.array(deepInterviewBoundedString(200).min(1)).min(1).max(5),
|
|
85
113
|
})
|
|
86
114
|
.strict();
|
|
87
115
|
|
|
@@ -92,53 +120,71 @@ const DeepInterviewIntentReview = z
|
|
|
92
120
|
.array(
|
|
93
121
|
z
|
|
94
122
|
.object({
|
|
95
|
-
removed_id:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
replacement_ids: z
|
|
99
|
-
.array(z.string().regex(/^(artifact|surface|integration|constraint):[a-z0-9][a-z0-9._/-]{0,127}$/))
|
|
100
|
-
.min(1)
|
|
101
|
-
.max(64),
|
|
102
|
-
rationale: z.string().min(1).max(500),
|
|
123
|
+
removed_id: DeepInterviewReferenceId,
|
|
124
|
+
replacement_ids: z.array(DeepInterviewReferenceId).min(1).max(64),
|
|
125
|
+
rationale: deepInterviewBoundedString(500).min(1),
|
|
103
126
|
})
|
|
104
127
|
.strict(),
|
|
105
128
|
)
|
|
106
129
|
.max(64),
|
|
107
|
-
approval_options: z.array(
|
|
130
|
+
approval_options: z.array(deepInterviewBoundedString(200).min(1)).min(1).max(5),
|
|
108
131
|
})
|
|
109
132
|
.strict();
|
|
110
133
|
|
|
111
134
|
/** Optional structured deep-interview round metadata; when present the round is recorded automatically. */
|
|
112
|
-
const
|
|
113
|
-
.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
135
|
+
const DeepInterviewMetadata = z.object({
|
|
136
|
+
round_id: deepInterviewBoundedString(128).describe("stable optional round identity").optional(),
|
|
137
|
+
round: z.number().int().nonnegative().describe("round number"),
|
|
138
|
+
component: deepInterviewBoundedString(128).min(1).describe("targeted topology component"),
|
|
139
|
+
dimension: deepInterviewBoundedString(128).min(1).describe("targeted clarity dimension"),
|
|
140
|
+
ambiguity: z.number().min(0).max(1).describe("ambiguity at ask time (0..1)"),
|
|
141
|
+
confused_terms: z
|
|
142
|
+
.array(deepInterviewBoundedString(256).min(1))
|
|
143
|
+
.max(32)
|
|
144
|
+
.describe("explicit terms the user does not understand; glossary help only, never inferred")
|
|
145
|
+
.optional(),
|
|
146
|
+
references: z
|
|
147
|
+
.array(
|
|
148
|
+
z
|
|
149
|
+
.object({
|
|
150
|
+
reference_id: deepInterviewBoundedString(256).min(1),
|
|
151
|
+
label: deepInterviewBoundedString(256).min(1),
|
|
152
|
+
origin: deepInterviewBoundedString(256).min(1),
|
|
153
|
+
url: deepInterviewBoundedString(2048).min(1).optional(),
|
|
154
|
+
excerpt: deepInterviewBoundedString(2048).min(1).optional(),
|
|
155
|
+
})
|
|
156
|
+
.strict(),
|
|
127
157
|
)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
158
|
+
.max(32)
|
|
159
|
+
.describe("inert reference context for contrast questions only; url/excerpt are never auto-fetched")
|
|
160
|
+
.optional(),
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const DeepInterviewMeta = z.union([
|
|
164
|
+
DeepInterviewMetadata.strict(),
|
|
165
|
+
DeepInterviewMetadata.extend({
|
|
166
|
+
round: z.literal(0),
|
|
167
|
+
component: z.literal("review-topology"),
|
|
168
|
+
dimension: z.literal("topology"),
|
|
169
|
+
intent_contract: DeepInterviewIntentContract,
|
|
170
|
+
}).strict(),
|
|
171
|
+
DeepInterviewMetadata.extend({
|
|
172
|
+
round: z.number().int().positive(),
|
|
173
|
+
intent_review: DeepInterviewIntentReview,
|
|
174
|
+
}).strict(),
|
|
175
|
+
]);
|
|
176
|
+
|
|
177
|
+
type DeepInterviewMeta = z.infer<typeof DeepInterviewMeta>;
|
|
178
|
+
|
|
179
|
+
function intentContract(
|
|
180
|
+
metadata: DeepInterviewMeta | undefined,
|
|
181
|
+
): z.infer<typeof DeepInterviewIntentContract> | undefined {
|
|
182
|
+
return metadata && "intent_contract" in metadata ? metadata.intent_contract : undefined;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function intentReview(metadata: DeepInterviewMeta | undefined): z.infer<typeof DeepInterviewIntentReview> | undefined {
|
|
186
|
+
return metadata && "intent_review" in metadata ? metadata.intent_review : undefined;
|
|
187
|
+
}
|
|
142
188
|
|
|
143
189
|
const WorkflowGateMeta = z.object({
|
|
144
190
|
stage: z.enum(["deep-interview", "ralplan", "ultragoal"]).describe("workflow gate stage"),
|
|
@@ -157,23 +203,48 @@ const QuestionItem = z
|
|
|
157
203
|
})
|
|
158
204
|
.superRefine((value, context) => {
|
|
159
205
|
const labels = new Set(value.options.map(option => option.label));
|
|
160
|
-
|
|
206
|
+
const contract = intentContract(value.deepInterview);
|
|
207
|
+
const review = intentReview(value.deepInterview);
|
|
208
|
+
if (contract && review)
|
|
209
|
+
context.addIssue({
|
|
210
|
+
code: "custom",
|
|
211
|
+
message: "intent contract and review are mutually exclusive",
|
|
212
|
+
path: ["deepInterview"],
|
|
213
|
+
});
|
|
214
|
+
if (
|
|
215
|
+
contract &&
|
|
216
|
+
(value.deepInterview?.round !== 0 ||
|
|
217
|
+
value.deepInterview.component !== "review-topology" ||
|
|
218
|
+
value.deepInterview.dimension !== "topology")
|
|
219
|
+
)
|
|
220
|
+
context.addIssue({
|
|
221
|
+
code: "custom",
|
|
222
|
+
message: "intent contract requires round-0 review topology metadata",
|
|
223
|
+
path: ["deepInterview"],
|
|
224
|
+
});
|
|
225
|
+
if (review && (value.deepInterview?.round ?? 0) <= 0)
|
|
226
|
+
context.addIssue({
|
|
227
|
+
code: "custom",
|
|
228
|
+
message: "intent review requires a positive round",
|
|
229
|
+
path: ["deepInterview", "round"],
|
|
230
|
+
});
|
|
231
|
+
if ((contract || review) && value.multi === true)
|
|
161
232
|
context.addIssue({ code: "custom", message: "intent gates must be single-select", path: ["multi"] });
|
|
162
|
-
const confirmationOptions =
|
|
233
|
+
const confirmationOptions = contract?.confirmation_options ?? [];
|
|
163
234
|
if (new Set(confirmationOptions).size !== confirmationOptions.length)
|
|
164
235
|
context.addIssue({
|
|
165
236
|
code: "custom",
|
|
166
237
|
message: "intent confirmation options must be unique",
|
|
167
238
|
path: ["deepInterview", "intent_contract"],
|
|
168
239
|
});
|
|
169
|
-
const approvalOptions =
|
|
240
|
+
const approvalOptions = review?.approval_options ?? [];
|
|
170
241
|
if (new Set(approvalOptions).size !== approvalOptions.length)
|
|
171
242
|
context.addIssue({
|
|
172
243
|
code: "custom",
|
|
173
244
|
message: "intent approval options must be unique",
|
|
174
245
|
path: ["deepInterview", "intent_review"],
|
|
175
246
|
});
|
|
176
|
-
for (const label of
|
|
247
|
+
for (const label of confirmationOptions) {
|
|
177
248
|
if (!labels.has(label))
|
|
178
249
|
context.addIssue({
|
|
179
250
|
code: "custom",
|
|
@@ -181,7 +252,7 @@ const QuestionItem = z
|
|
|
181
252
|
path: ["deepInterview", "intent_contract"],
|
|
182
253
|
});
|
|
183
254
|
}
|
|
184
|
-
for (const label of
|
|
255
|
+
for (const label of approvalOptions) {
|
|
185
256
|
if (!labels.has(label))
|
|
186
257
|
context.addIssue({
|
|
187
258
|
code: "custom",
|
|
@@ -197,6 +268,195 @@ export const askSchema = z.object({
|
|
|
197
268
|
|
|
198
269
|
export type AskToolInput = z.infer<typeof askSchema>;
|
|
199
270
|
|
|
271
|
+
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
272
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
273
|
+
const prototype = Object.getPrototypeOf(value);
|
|
274
|
+
return prototype === Object.prototype || prototype === null;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function isOnlyPlainData(value: unknown): boolean {
|
|
278
|
+
if (Array.isArray(value))
|
|
279
|
+
return (
|
|
280
|
+
Reflect.ownKeys(value).length === value.length + 1 &&
|
|
281
|
+
value.every((item, index) => Object.hasOwn(value, index) && isOnlyPlainData(item))
|
|
282
|
+
);
|
|
283
|
+
if (typeof value !== "object" || value === null) return true;
|
|
284
|
+
return isPlainRecord(value) && Object.values(value).every(isOnlyPlainData);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function hasExactOwnKeys(value: Record<string, unknown>, allowed: readonly string[]): boolean {
|
|
288
|
+
const keys = Reflect.ownKeys(value);
|
|
289
|
+
return keys.length === allowed.length && keys.every(key => typeof key === "string" && allowed.includes(key));
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function hasOnlyAllowedOwnKeys(value: Record<string, unknown>, allowed: readonly string[]): boolean {
|
|
293
|
+
return Reflect.ownKeys(value).every(key => typeof key === "string" && allowed.includes(key));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function hasUniqueDisplayedLabels(labels: readonly string[], optionLabels: ReadonlySet<string>): boolean {
|
|
297
|
+
return new Set(labels).size === labels.length && labels.every(label => optionLabels.has(label));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** Parse only to recognize a retired recovery shape; parsed values are never eligible for recovery. */
|
|
301
|
+
function parseEncodedContainer(value: unknown): unknown {
|
|
302
|
+
if (typeof value !== "string") return value;
|
|
303
|
+
try {
|
|
304
|
+
return JSON.parse(value);
|
|
305
|
+
} catch {
|
|
306
|
+
return value;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** Whether malformed input is close enough to the retired pair shape to require a terminal rejection. */
|
|
311
|
+
function isRoundZeroRecoveryCandidate(value: unknown): boolean {
|
|
312
|
+
const root = parseEncodedContainer(value);
|
|
313
|
+
if (typeof root !== "object" || root === null || !Object.hasOwn(root, "questions")) return false;
|
|
314
|
+
const questionsValue = parseEncodedContainer((root as Record<string, unknown>).questions);
|
|
315
|
+
if (!Array.isArray(questionsValue)) return questionsValue === null;
|
|
316
|
+
return questionsValue.some(rawQuestion => {
|
|
317
|
+
const question = parseEncodedContainer(rawQuestion);
|
|
318
|
+
if (typeof question !== "object" || question === null || !Object.hasOwn(question, "deepInterview")) return false;
|
|
319
|
+
const deepInterview = parseEncodedContainer((question as Record<string, unknown>).deepInterview);
|
|
320
|
+
if (typeof deepInterview !== "object" || deepInterview === null) return deepInterview === null;
|
|
321
|
+
const metadata = deepInterview as Record<string, unknown>;
|
|
322
|
+
return Object.hasOwn(metadata, "intent_contract") || Object.hasOwn(metadata, "intent_review");
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** Remove only strict-provider null placeholders for fields optional in the canonical Ask contract. */
|
|
327
|
+
function normalizeRoundZeroOptionalNulls(arguments_: Record<string, unknown>): Record<string, unknown> {
|
|
328
|
+
if (!isPlainRecord(arguments_) || !Array.isArray(arguments_.questions) || arguments_.questions.length !== 1)
|
|
329
|
+
return arguments_;
|
|
330
|
+
const question = arguments_.questions[0];
|
|
331
|
+
if (!isPlainRecord(question) || !isPlainRecord(question.deepInterview)) return arguments_;
|
|
332
|
+
const normalizedQuestion = { ...question };
|
|
333
|
+
let changed = false;
|
|
334
|
+
for (const key of ["multi", "recommended", "workflowGate"] as const) {
|
|
335
|
+
if (Object.hasOwn(normalizedQuestion, key) && normalizedQuestion[key] === null) {
|
|
336
|
+
delete normalizedQuestion[key];
|
|
337
|
+
changed = true;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const normalizedDeepInterview = { ...question.deepInterview };
|
|
341
|
+
for (const key of ["round_id", "confused_terms", "references"] as const) {
|
|
342
|
+
if (Object.hasOwn(normalizedDeepInterview, key) && normalizedDeepInterview[key] === null) {
|
|
343
|
+
delete normalizedDeepInterview[key];
|
|
344
|
+
changed = true;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (Array.isArray(normalizedDeepInterview.references)) {
|
|
348
|
+
const references = normalizedDeepInterview.references.map(reference => {
|
|
349
|
+
if (!isPlainRecord(reference)) return reference;
|
|
350
|
+
const normalizedReference = { ...reference };
|
|
351
|
+
for (const key of ["url", "excerpt"] as const) {
|
|
352
|
+
if (Object.hasOwn(normalizedReference, key) && normalizedReference[key] === null) {
|
|
353
|
+
delete normalizedReference[key];
|
|
354
|
+
changed = true;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return normalizedReference;
|
|
358
|
+
});
|
|
359
|
+
normalizedDeepInterview.references = references;
|
|
360
|
+
}
|
|
361
|
+
if (changed) normalizedQuestion.deepInterview = normalizedDeepInterview;
|
|
362
|
+
return changed ? { ...arguments_, questions: [normalizedQuestion] } : arguments_;
|
|
363
|
+
}
|
|
364
|
+
function recoverRoundZeroIntentContract(arguments_: Record<string, unknown>): RawArgumentValidationResult {
|
|
365
|
+
if (!isRoundZeroRecoveryCandidate(arguments_)) return { outcome: "passthrough" };
|
|
366
|
+
const normalizedArguments = normalizeRoundZeroOptionalNulls(arguments_);
|
|
367
|
+
if (!isOnlyPlainData(normalizedArguments) || !isPlainRecord(normalizedArguments)) return { outcome: "reject" };
|
|
368
|
+
if (
|
|
369
|
+
!hasExactOwnKeys(normalizedArguments, ["questions"]) ||
|
|
370
|
+
!Array.isArray(normalizedArguments.questions) ||
|
|
371
|
+
normalizedArguments.questions.length !== 1
|
|
372
|
+
)
|
|
373
|
+
return { outcome: "reject" };
|
|
374
|
+
|
|
375
|
+
const question = normalizedArguments.questions[0];
|
|
376
|
+
if (!isPlainRecord(question)) return { outcome: "reject" };
|
|
377
|
+
const questionKeys = ["id", "question", "options", "multi", "recommended", "deepInterview", "workflowGate"];
|
|
378
|
+
if (!hasOnlyAllowedOwnKeys(question, questionKeys)) return { outcome: "reject" };
|
|
379
|
+
if (
|
|
380
|
+
typeof question.id !== "string" ||
|
|
381
|
+
typeof question.question !== "string" ||
|
|
382
|
+
!Array.isArray(question.options) ||
|
|
383
|
+
!Object.hasOwn(question, "deepInterview") ||
|
|
384
|
+
!isPlainRecord(question.deepInterview) ||
|
|
385
|
+
(Object.hasOwn(question, "multi") && question.multi !== false) ||
|
|
386
|
+
(Object.hasOwn(question, "recommended") && typeof question.recommended !== "number")
|
|
387
|
+
)
|
|
388
|
+
return { outcome: "reject" };
|
|
389
|
+
const deepInterview = question.deepInterview;
|
|
390
|
+
const hasIntentContract = Object.hasOwn(deepInterview, "intent_contract");
|
|
391
|
+
const hasIntentReview = Object.hasOwn(deepInterview, "intent_review");
|
|
392
|
+
if (hasIntentContract !== hasIntentReview && askSchema.safeParse(normalizedArguments).success)
|
|
393
|
+
return { outcome: "passthrough" };
|
|
394
|
+
|
|
395
|
+
if (
|
|
396
|
+
Object.hasOwn(question, "workflowGate") &&
|
|
397
|
+
(!isPlainRecord(question.workflowGate) ||
|
|
398
|
+
!hasExactOwnKeys(question.workflowGate, ["stage", "kind"]) ||
|
|
399
|
+
question.workflowGate.stage !== "deep-interview" ||
|
|
400
|
+
question.workflowGate.kind !== "question")
|
|
401
|
+
)
|
|
402
|
+
return { outcome: "reject" };
|
|
403
|
+
|
|
404
|
+
if (
|
|
405
|
+
!question.options.every(
|
|
406
|
+
option => isPlainRecord(option) && hasExactOwnKeys(option, ["label"]) && typeof option.label === "string",
|
|
407
|
+
)
|
|
408
|
+
)
|
|
409
|
+
return { outcome: "reject" };
|
|
410
|
+
const optionLabels = question.options.map(option => (option as { label: string }).label);
|
|
411
|
+
if (new Set(optionLabels).size !== optionLabels.length) return { outcome: "reject" };
|
|
412
|
+
|
|
413
|
+
const deepInterviewKeys = [
|
|
414
|
+
"round_id",
|
|
415
|
+
"round",
|
|
416
|
+
"component",
|
|
417
|
+
"dimension",
|
|
418
|
+
"ambiguity",
|
|
419
|
+
"confused_terms",
|
|
420
|
+
"references",
|
|
421
|
+
"intent_contract",
|
|
422
|
+
"intent_review",
|
|
423
|
+
];
|
|
424
|
+
if (
|
|
425
|
+
!hasOnlyAllowedOwnKeys(deepInterview, deepInterviewKeys) ||
|
|
426
|
+
!Object.hasOwn(deepInterview, "intent_contract") ||
|
|
427
|
+
!Object.hasOwn(deepInterview, "intent_review") ||
|
|
428
|
+
deepInterview.round !== 0 ||
|
|
429
|
+
typeof deepInterview.component !== "string" ||
|
|
430
|
+
deepInterview.component !== "review-topology" ||
|
|
431
|
+
typeof deepInterview.dimension !== "string" ||
|
|
432
|
+
deepInterview.dimension !== "topology" ||
|
|
433
|
+
typeof deepInterview.ambiguity !== "number" ||
|
|
434
|
+
(Object.hasOwn(deepInterview, "round_id") && typeof deepInterview.round_id !== "string")
|
|
435
|
+
)
|
|
436
|
+
return { outcome: "reject" };
|
|
437
|
+
|
|
438
|
+
const contract = DeepInterviewIntentContract.safeParse(deepInterview.intent_contract);
|
|
439
|
+
const review = DeepInterviewIntentReview.safeParse(deepInterview.intent_review);
|
|
440
|
+
if (!contract.success || !review.success) return { outcome: "reject" };
|
|
441
|
+
const displayedLabels = new Set(optionLabels);
|
|
442
|
+
if (
|
|
443
|
+
!hasUniqueDisplayedLabels(contract.data.confirmation_options, displayedLabels) ||
|
|
444
|
+
!hasUniqueDisplayedLabels(review.data.approval_options, displayedLabels)
|
|
445
|
+
)
|
|
446
|
+
return { outcome: "reject" };
|
|
447
|
+
|
|
448
|
+
const { intent_review: _intentReview, ...recoveredDeepInterview } = deepInterview;
|
|
449
|
+
const recovered = {
|
|
450
|
+
questions: [
|
|
451
|
+
{
|
|
452
|
+
...question,
|
|
453
|
+
deepInterview: { ...recoveredDeepInterview, intent_contract: contract.data },
|
|
454
|
+
},
|
|
455
|
+
],
|
|
456
|
+
};
|
|
457
|
+
return askSchema.safeParse(recovered).success ? { outcome: "accept", arguments: recovered } : { outcome: "reject" };
|
|
458
|
+
}
|
|
459
|
+
|
|
200
460
|
/** Result for a single question */
|
|
201
461
|
export interface QuestionResult {
|
|
202
462
|
id: string;
|
|
@@ -803,6 +1063,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
803
1063
|
readonly summary = "Ask the user a clarifying question";
|
|
804
1064
|
readonly description: string;
|
|
805
1065
|
readonly parameters = askSchema;
|
|
1066
|
+
readonly rawArgumentValidation = recoverRoundZeroIntentContract;
|
|
806
1067
|
readonly strict = true;
|
|
807
1068
|
readonly loadMode = "discoverable";
|
|
808
1069
|
|
|
@@ -811,7 +1072,9 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
811
1072
|
}
|
|
812
1073
|
|
|
813
1074
|
static createIf(session: ToolSession): AskTool | null {
|
|
814
|
-
return session.hasUI || session.getWorkflowGateEmitter?.()
|
|
1075
|
+
return session.hasUI || session.workflowGateEligible || session.getWorkflowGateEmitter?.()
|
|
1076
|
+
? new AskTool(session)
|
|
1077
|
+
: null;
|
|
815
1078
|
}
|
|
816
1079
|
|
|
817
1080
|
/** Send terminal notification when ask tool is waiting for input */
|
|
@@ -832,6 +1095,8 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
832
1095
|
customInput: string | undefined,
|
|
833
1096
|
): Promise<void> {
|
|
834
1097
|
const meta = q.deepInterview;
|
|
1098
|
+
if (customInput !== undefined && (meta || isDeepInterviewAskQuestion(q.question)))
|
|
1099
|
+
assertDeepInterviewInputWithinLimit(customInput, MAX_USER_RESPONSE_LENGTH, "user_response");
|
|
835
1100
|
if (!meta) return;
|
|
836
1101
|
const cwd = this.session.cwd;
|
|
837
1102
|
const sessionId = this.session.getSessionId?.() ?? undefined;
|
|
@@ -850,14 +1115,14 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
850
1115
|
ambiguity: meta.ambiguity,
|
|
851
1116
|
selectedOptions,
|
|
852
1117
|
customInput,
|
|
853
|
-
intent_contract: meta
|
|
854
|
-
intent_review: meta
|
|
1118
|
+
intent_contract: intentContract(meta),
|
|
1119
|
+
intent_review: intentReview(meta),
|
|
855
1120
|
},
|
|
856
1121
|
{ sessionId },
|
|
857
1122
|
).then(async () => {
|
|
858
1123
|
await syncDeepInterviewRecorderHud(cwd, statePath, sessionId);
|
|
859
1124
|
}),
|
|
860
|
-
meta
|
|
1125
|
+
intentContract(meta) !== undefined || intentReview(meta) !== undefined,
|
|
861
1126
|
);
|
|
862
1127
|
}
|
|
863
1128
|
|
|
@@ -872,6 +1137,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
872
1137
|
activeSkillState: this.session.getActiveSkillState?.(),
|
|
873
1138
|
sessionId: this.session.getSessionId?.() ?? null,
|
|
874
1139
|
});
|
|
1140
|
+
assertDeepInterviewStructuredResponseWithinLimit(params);
|
|
875
1141
|
let activeRemoteReceipt: AskRemoteReceipt | undefined;
|
|
876
1142
|
let activeRemoteRequest: AskAnswerRequest | undefined;
|
|
877
1143
|
let remoteGeneration = 0;
|
|
@@ -1190,7 +1456,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
1190
1456
|
navigation: options?.navigation,
|
|
1191
1457
|
scrollTitleRows: isDeepInterviewQuestion ? DEEP_INTERVIEW_SELECTOR_SCROLL_TITLE_ROWS : undefined,
|
|
1192
1458
|
otherOptionLabel,
|
|
1193
|
-
autoSelectOnTimeout: !q.deepInterview
|
|
1459
|
+
autoSelectOnTimeout: !intentContract(q.deepInterview) && !intentReview(q.deepInterview),
|
|
1194
1460
|
clarificationOptionLabel,
|
|
1195
1461
|
onRemoteState: state => {
|
|
1196
1462
|
activeRemoteRequest = {
|
|
@@ -1216,6 +1482,8 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
1216
1482
|
return displayIndex >= 0 ? (rawOptionLabels[displayIndex] ?? selected) : selected;
|
|
1217
1483
|
})
|
|
1218
1484
|
: displaySelectedOptions;
|
|
1485
|
+
if ((isDeepInterviewQuestion || isDeepInterviewAskQuestion(q.question)) && customInput !== undefined)
|
|
1486
|
+
assertDeepInterviewInputWithinLimit(customInput, MAX_USER_RESPONSE_LENGTH, "user_response");
|
|
1219
1487
|
if (activeRemoteReceipt) {
|
|
1220
1488
|
const settlement: AskSettlement =
|
|
1221
1489
|
clarificationQuestion !== undefined
|
|
@@ -1250,10 +1518,14 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
1250
1518
|
timedOut,
|
|
1251
1519
|
};
|
|
1252
1520
|
} catch (error) {
|
|
1253
|
-
await settleActiveRemote(
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1521
|
+
await settleActiveRemote(
|
|
1522
|
+
error instanceof Error && error.message.includes("exceeds max length")
|
|
1523
|
+
? { kind: "invalid", reason: "invalid_structured_answer" }
|
|
1524
|
+
: {
|
|
1525
|
+
kind: "resolve_without_commit",
|
|
1526
|
+
reason: error instanceof Error && error.name === "AbortError" ? "aborted" : "exception",
|
|
1527
|
+
},
|
|
1528
|
+
);
|
|
1257
1529
|
activeRemoteRequest = undefined;
|
|
1258
1530
|
if (error instanceof Error && error.name === "AbortError") {
|
|
1259
1531
|
throw new ToolAbortError("Ask input was cancelled");
|
|
@@ -1277,7 +1549,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
1277
1549
|
}
|
|
1278
1550
|
if (
|
|
1279
1551
|
clarificationQuestion === undefined &&
|
|
1280
|
-
!(timedOut && (q.deepInterview
|
|
1552
|
+
!(timedOut && (intentContract(q.deepInterview) || intentReview(q.deepInterview)))
|
|
1281
1553
|
) {
|
|
1282
1554
|
await this.#recordDeepInterviewRound(q, selectedOptions, customInput);
|
|
1283
1555
|
}
|
|
@@ -1358,7 +1630,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
1358
1630
|
|
|
1359
1631
|
if (
|
|
1360
1632
|
clarificationQuestion === undefined &&
|
|
1361
|
-
!(timedOut && (q.deepInterview
|
|
1633
|
+
!(timedOut && (intentContract(q.deepInterview) || intentReview(q.deepInterview)))
|
|
1362
1634
|
) {
|
|
1363
1635
|
await this.#recordDeepInterviewRound(q, selectedOptions, customInput);
|
|
1364
1636
|
}
|