@gajae-code/coding-agent 0.11.6 → 0.11.8
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 +92 -0
- package/dist/types/capability/index.d.ts +10 -20
- package/dist/types/capability/types.d.ts +3 -0
- package/dist/types/cli/args.d.ts +0 -2
- package/dist/types/commands/daemon.d.ts +2 -2
- package/dist/types/commands/sdk.d.ts +20 -2
- package/dist/types/config/keybindings.d.ts +34 -4
- 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/settings-schema.d.ts +70 -7
- 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/runner-artifact.d.ts +8 -0
- package/dist/types/eval/py/tool-bridge.d.ts +1 -2
- package/dist/types/gjc-runtime/repository-binding.d.ts +63 -0
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +6 -5
- package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +20 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +43 -5
- package/dist/types/main.d.ts +32 -1
- package/dist/types/modes/components/btw-panel.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +2 -0
- package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +2 -1
- package/dist/types/modes/components/tool-status-header.d.ts +4 -2
- package/dist/types/modes/components/welcome.d.ts +2 -0
- package/dist/types/modes/controllers/btw-controller.d.ts +5 -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 +7 -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 +4 -0
- package/dist/types/modes/utils/hotkeys-markdown.d.ts +2 -1
- package/dist/types/runtime-mcp/content-limits.d.ts +7 -0
- 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/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 +6 -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 +26 -1
- package/dist/types/sdk/bus/lifecycle-commands.d.ts +5 -4
- package/dist/types/sdk/bus/notification-orchestration.d.ts +1 -0
- package/dist/types/sdk/bus/notification-service.d.ts +34 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +105 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +18 -3
- package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +189 -4
- package/dist/types/sdk/bus/telegram-reference.d.ts +2 -0
- package/dist/types/sdk/bus/topic-registry.d.ts +88 -2
- package/dist/types/sdk/client/discovery.d.ts +2 -0
- package/dist/types/sdk/client/liveness.d.ts +7 -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/sdk/transport/auth-preface.d.ts +9 -0
- package/dist/types/sdk/transport/index.d.ts +12 -0
- package/dist/types/sdk/transport/relay.d.ts +43 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +4 -0
- package/dist/types/sdk/transport/socket.d.ts +7 -0
- package/dist/types/sdk/transport/stdio.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +30 -5
- package/dist/types/session/btw-contract.d.ts +16 -0
- package/dist/types/session/internal/managed-session-scope.d.ts +32 -9
- package/dist/types/session/internal/managed-session-storage.d.ts +27 -10
- package/dist/types/session/internal/native-publish-outcome.d.ts +48 -0
- package/dist/types/session/session-manager.d.ts +25 -4
- package/dist/types/session/session-storage.d.ts +18 -0
- package/dist/types/setup/credential-import.d.ts +5 -1
- package/dist/types/setup/provider-onboarding.d.ts +2 -0
- package/dist/types/ssh/utils.d.ts +1 -0
- package/dist/types/task/discovery.d.ts +3 -1
- 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/fetch.d.ts +5 -8
- package/dist/types/tools/image-gen.d.ts +63 -1
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/tools/skill-discovery.d.ts +7 -0
- package/dist/types/tools/sqlite-reader.d.ts +7 -0
- package/dist/types/utils/prompt-suggestion.d.ts +33 -0
- package/dist/types/utils/shell-snapshot.d.ts +12 -0
- package/dist/types/web/insane/bridge.d.ts +1 -0
- package/dist/types/web/insane/url-guard.d.ts +17 -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 +4 -0
- package/scripts/run-test-manifest.ts +4 -4
- package/src/capability/index.ts +50 -42
- package/src/capability/types.ts +3 -0
- package/src/cli/args.ts +2 -92
- package/src/cli/notify-cli.ts +5 -38
- package/src/cli/setup-cli.ts +10 -4
- package/src/cli.ts +52 -9
- package/src/commands/daemon.ts +22 -5
- package/src/commands/sdk.ts +44 -11
- package/src/config/keybindings.ts +158 -25
- package/src/config/model-profile-activation.ts +7 -18
- package/src/config/model-profile-contract.ts +161 -0
- package/src/config/model-profiles.ts +18 -0
- package/src/config/model-registry.ts +11 -1
- package/src/config/settings-schema.ts +73 -7
- package/src/coordinator-mcp/model-preset.ts +21 -74
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +2 -2
- package/src/defaults/gjc/skills/ralplan/SKILL.md +3 -1
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +42 -1
- package/src/discovery/ssh.ts +12 -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/kernel.ts +1 -21
- package/src/eval/py/prelude.py +2 -2
- package/src/eval/py/runner-artifact.ts +84 -0
- package/src/eval/py/tool-bridge.ts +34 -18
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +2 -1
- package/src/extensibility/plugins/marketplace/fetcher.ts +191 -7
- 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 +75 -3
- package/src/gjc-runtime/repository-binding.ts +267 -0
- package/src/gjc-runtime/session-state-sidecar.ts +25 -9
- 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 +93 -15
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +103 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +456 -31
- package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
- package/src/gjc-runtime/workflow-manifest.ts +23 -0
- package/src/internal-urls/docs-index.generated.ts +12 -10
- package/src/internal-urls/local-protocol.ts +64 -21
- package/src/internal-urls/mcp-protocol.ts +73 -11
- package/src/main.ts +111 -37
- package/src/modes/DESIGN.md +5 -0
- package/src/modes/acp/acp-agent.ts +50 -16
- package/src/modes/action-registry.ts +1 -1
- package/src/modes/components/btw-panel.ts +82 -16
- package/src/modes/components/model-selector.ts +73 -11
- 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 +25 -4
- package/src/modes/components/welcome.ts +22 -18
- package/src/modes/controllers/btw-controller.ts +119 -36
- 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 +38 -2
- package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -6
- package/src/modes/controllers/selector-controller.ts +149 -63
- package/src/modes/interactive-mode.ts +57 -34
- 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 +4 -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/btw-user.md +3 -8
- package/src/prompts/system/prompt-suggestion-system.md +32 -0
- package/src/prompts/system/system-prompt.md +6 -4
- package/src/prompts/tools/cron.md +3 -1
- package/src/prompts/tools/skill-discovery.md +1 -0
- package/src/runtime-mcp/client.ts +41 -55
- package/src/runtime-mcp/content-limits.ts +71 -0
- package/src/runtime-mcp/json-rpc.ts +23 -9
- 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/transports/http.ts +63 -22
- package/src/sdk/broker/broker.ts +34 -20
- package/src/sdk/broker/lifecycle.ts +185 -36
- package/src/sdk/bus/chat-daemon-control.ts +19 -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 +244 -70
- package/src/sdk/bus/lifecycle-commands.ts +63 -10
- 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 +68 -20
- 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 +18 -3
- package/src/sdk/bus/telegram-daemon-control.ts +147 -35
- package/src/sdk/bus/telegram-daemon.ts +2463 -450
- package/src/sdk/bus/telegram-reference.ts +20 -2
- package/src/sdk/bus/topic-registry.ts +451 -12
- package/src/sdk/cli/session-cli.ts +15 -2
- package/src/sdk/client/discovery.ts +19 -3
- package/src/sdk/client/liveness.ts +49 -0
- 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 +58 -0
- package/src/sdk/protocol/operation-registry.ts +18 -4
- package/src/sdk/session-directory.ts +5 -1
- package/src/sdk/session.ts +31 -5
- package/src/sdk/startup-capability.ts +9 -1
- package/src/sdk/transport/auth-preface.ts +82 -0
- package/src/sdk/transport/index.ts +18 -0
- package/src/sdk/transport/relay.ts +252 -0
- package/src/sdk/transport/serve-cli.ts +105 -0
- package/src/sdk/transport/socket.ts +197 -0
- package/src/sdk/transport/stdio.ts +17 -0
- package/src/session/agent-session.ts +292 -154
- package/src/session/blob-store.ts +25 -7
- package/src/session/btw-contract.ts +59 -0
- package/src/session/internal/managed-session-scope.ts +1163 -137
- package/src/session/internal/managed-session-storage.ts +397 -234
- package/src/session/internal/native-publish-outcome.ts +266 -0
- package/src/session/session-manager.ts +620 -403
- package/src/session/session-storage.ts +128 -11
- package/src/setup/credential-import.ts +28 -3
- package/src/setup/provider-onboarding.ts +30 -1
- package/src/setup/provider-presets.json +2 -1
- package/src/slash-commands/builtin-registry.ts +1 -1
- package/src/ssh/utils.ts +14 -0
- package/src/task/discovery.ts +8 -2
- 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 -89
- 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/fetch.ts +13 -131
- package/src/tools/image-gen.ts +476 -23
- package/src/tools/index.ts +4 -0
- package/src/tools/read.ts +2 -0
- package/src/tools/render-utils.ts +7 -1
- package/src/tools/skill-discovery.ts +39 -3
- package/src/tools/sqlite-reader.ts +63 -5
- package/src/utils/edit-mode.ts +2 -2
- package/src/utils/pasted-image-path.ts +15 -6
- package/src/utils/prompt-suggestion.ts +265 -0
- package/src/utils/shell-snapshot.ts +173 -63
- package/src/utils/sixel.ts +3 -3
- package/src/web/insane/bridge.ts +5 -1
- package/src/web/insane/url-guard.ts +140 -27
- package/src/web/scrapers/docs-rs.ts +30 -5
- package/src/web/scrapers/types.ts +19 -49
- package/src/web/scrapers/utils.ts +12 -26
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { formatModelProfileDisplayLabel, type ModelProfileDefinition } from "./model-profiles";
|
|
2
|
+
|
|
3
|
+
export const MODEL_PROFILE_DISCOVERY_QUERY = "models.profiles.list";
|
|
4
|
+
export const MODEL_PROFILE_ERROR_DETAIL_MAX_BYTES = 2048;
|
|
5
|
+
const REQUESTED_PROFILE_MAX_BYTES = 256;
|
|
6
|
+
|
|
7
|
+
const LEGACY_MODEL_PROFILE_ALIASES: ReadonlyMap<string, string> = new Map([["codex-standard", "codex-medium"]]);
|
|
8
|
+
|
|
9
|
+
export interface ModelProfileCatalogItem {
|
|
10
|
+
id: string;
|
|
11
|
+
displayName: string;
|
|
12
|
+
source: "builtin" | "configured";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface UnknownModelProfileDetails {
|
|
16
|
+
requestedProfile: string;
|
|
17
|
+
availableProfiles: string[];
|
|
18
|
+
discoveryQuery: typeof MODEL_PROFILE_DISCOVERY_QUERY;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ModelProfileRegistryErrorDetails {
|
|
22
|
+
requestedProfile?: string;
|
|
23
|
+
availableProfiles: [];
|
|
24
|
+
discoveryQuery: typeof MODEL_PROFILE_DISCOVERY_QUERY;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type ModelProfileErrorDetails = UnknownModelProfileDetails | ModelProfileRegistryErrorDetails;
|
|
28
|
+
export type ModelProfileErrorCode = "unknown_model_profile" | "model_profile_registry_error";
|
|
29
|
+
|
|
30
|
+
function truncateUtf8(value: string, maxBytes: number): string {
|
|
31
|
+
if (Buffer.byteLength(value) <= maxBytes) return value;
|
|
32
|
+
let end = value.length;
|
|
33
|
+
while (end > 0 && Buffer.byteLength(value.slice(0, end)) > maxBytes) end--;
|
|
34
|
+
return value.slice(0, end);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function diagnosticProfileEcho(value: string): string {
|
|
38
|
+
return truncateUtf8(value.normalize("NFKC").replace(/[\p{Cc}\p{Cf}]+/gu, " "), REQUESTED_PROFILE_MAX_BYTES);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function boundedAvailableProfiles(requestedProfile: string, profiles: ReadonlyMap<string, unknown>): string[] {
|
|
42
|
+
const availableProfiles: string[] = [];
|
|
43
|
+
for (const id of [...new Set(profiles.keys())].sort((left, right) => left.localeCompare(right))) {
|
|
44
|
+
const candidate: UnknownModelProfileDetails = {
|
|
45
|
+
requestedProfile,
|
|
46
|
+
availableProfiles: [...availableProfiles, id],
|
|
47
|
+
discoveryQuery: MODEL_PROFILE_DISCOVERY_QUERY,
|
|
48
|
+
};
|
|
49
|
+
if (Buffer.byteLength(JSON.stringify(candidate)) > MODEL_PROFILE_ERROR_DETAIL_MAX_BYTES) continue;
|
|
50
|
+
availableProfiles.push(id);
|
|
51
|
+
}
|
|
52
|
+
return availableProfiles;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export class UnknownModelProfileError extends Error {
|
|
56
|
+
readonly code = "unknown_model_profile" as const;
|
|
57
|
+
readonly details: UnknownModelProfileDetails;
|
|
58
|
+
|
|
59
|
+
constructor(requestedProfile: string, profiles: ReadonlyMap<string, unknown>) {
|
|
60
|
+
const echoed = diagnosticProfileEcho(requestedProfile);
|
|
61
|
+
const availableProfiles = boundedAvailableProfiles(echoed, profiles);
|
|
62
|
+
const available = availableProfiles.length > 0 ? availableProfiles.join(", ") : "none";
|
|
63
|
+
super(
|
|
64
|
+
truncateUtf8(
|
|
65
|
+
`Unknown model profile ${JSON.stringify(echoed)}. Available profiles: ${available}. Query ${MODEL_PROFILE_DISCOVERY_QUERY} for the complete catalog.`,
|
|
66
|
+
512,
|
|
67
|
+
),
|
|
68
|
+
);
|
|
69
|
+
this.name = "UnknownModelProfileError";
|
|
70
|
+
this.details = {
|
|
71
|
+
requestedProfile: echoed,
|
|
72
|
+
availableProfiles,
|
|
73
|
+
discoveryQuery: MODEL_PROFILE_DISCOVERY_QUERY,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export class ModelProfileRegistryError extends Error {
|
|
79
|
+
readonly code = "model_profile_registry_error" as const;
|
|
80
|
+
readonly details: ModelProfileRegistryErrorDetails;
|
|
81
|
+
|
|
82
|
+
constructor(requestedProfile?: string) {
|
|
83
|
+
super(
|
|
84
|
+
`The model profile registry is unavailable. Query ${MODEL_PROFILE_DISCOVERY_QUERY} after fixing models.yml.`,
|
|
85
|
+
);
|
|
86
|
+
this.name = "ModelProfileRegistryError";
|
|
87
|
+
const echoed = requestedProfile === undefined ? undefined : diagnosticProfileEcho(requestedProfile);
|
|
88
|
+
this.details = {
|
|
89
|
+
...(echoed ? { requestedProfile: echoed } : {}),
|
|
90
|
+
availableProfiles: [],
|
|
91
|
+
discoveryQuery: MODEL_PROFILE_DISCOVERY_QUERY,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function resolveModelProfileName(profileName: string, profiles: ReadonlyMap<string, unknown>): string {
|
|
97
|
+
if (profiles.has(profileName)) return profileName;
|
|
98
|
+
const replacement = LEGACY_MODEL_PROFILE_ALIASES.get(profileName);
|
|
99
|
+
return replacement && profiles.has(replacement) ? replacement : profileName;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function validateModelProfileName(
|
|
103
|
+
profileName: string,
|
|
104
|
+
profiles: ReadonlyMap<string, unknown>,
|
|
105
|
+
registryError?: unknown,
|
|
106
|
+
): string {
|
|
107
|
+
if (registryError !== undefined) throw new ModelProfileRegistryError(profileName);
|
|
108
|
+
const resolved = resolveModelProfileName(profileName, profiles);
|
|
109
|
+
if (!profiles.has(resolved)) throw new UnknownModelProfileError(profileName, profiles);
|
|
110
|
+
return resolved;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function projectModelProfileCatalog(
|
|
114
|
+
profiles: ReadonlyMap<string, ModelProfileDefinition>,
|
|
115
|
+
registryError?: unknown,
|
|
116
|
+
): ModelProfileCatalogItem[] {
|
|
117
|
+
if (registryError !== undefined) throw new ModelProfileRegistryError();
|
|
118
|
+
return [...profiles.entries()]
|
|
119
|
+
.map(([id, definition]) => ({
|
|
120
|
+
id,
|
|
121
|
+
displayName: formatModelProfileDisplayLabel(definition),
|
|
122
|
+
source: definition.source === "user" ? ("configured" as const) : ("builtin" as const),
|
|
123
|
+
}))
|
|
124
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function isModelProfileError(value: unknown): value is {
|
|
128
|
+
code: ModelProfileErrorCode;
|
|
129
|
+
message: string;
|
|
130
|
+
details: ModelProfileErrorDetails;
|
|
131
|
+
} {
|
|
132
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
133
|
+
const error = value as Record<string, unknown>;
|
|
134
|
+
if (
|
|
135
|
+
(error.code !== "unknown_model_profile" && error.code !== "model_profile_registry_error") ||
|
|
136
|
+
typeof error.message !== "string" ||
|
|
137
|
+
!error.details ||
|
|
138
|
+
typeof error.details !== "object" ||
|
|
139
|
+
Array.isArray(error.details)
|
|
140
|
+
)
|
|
141
|
+
return false;
|
|
142
|
+
const details = error.details as Record<string, unknown>;
|
|
143
|
+
const detailKeys = Object.keys(details);
|
|
144
|
+
if (
|
|
145
|
+
!detailKeys.every(key => key === "requestedProfile" || key === "availableProfiles" || key === "discoveryQuery") ||
|
|
146
|
+
details.discoveryQuery !== MODEL_PROFILE_DISCOVERY_QUERY ||
|
|
147
|
+
!Array.isArray(details.availableProfiles) ||
|
|
148
|
+
!details.availableProfiles.every(id => typeof id === "string") ||
|
|
149
|
+
Buffer.byteLength(JSON.stringify(details)) > MODEL_PROFILE_ERROR_DETAIL_MAX_BYTES ||
|
|
150
|
+
(typeof details.requestedProfile === "string" &&
|
|
151
|
+
Buffer.byteLength(details.requestedProfile) > REQUESTED_PROFILE_MAX_BYTES)
|
|
152
|
+
)
|
|
153
|
+
return false;
|
|
154
|
+
if (error.code === "unknown_model_profile")
|
|
155
|
+
return detailKeys.length === 3 && typeof details.requestedProfile === "string";
|
|
156
|
+
return (
|
|
157
|
+
detailKeys.length === (details.requestedProfile === undefined ? 2 : 3) &&
|
|
158
|
+
details.availableProfiles.length === 0 &&
|
|
159
|
+
(details.requestedProfile === undefined || typeof details.requestedProfile === "string")
|
|
160
|
+
);
|
|
161
|
+
}
|
|
@@ -257,6 +257,20 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
|
257
257
|
critic: "minimax-code/minimax-m3:xhigh",
|
|
258
258
|
architect: "minimax-code/minimax-m3:xhigh",
|
|
259
259
|
}),
|
|
260
|
+
profile("alibaba-token-plan-balanced", ["alibaba-token-plan"], {
|
|
261
|
+
default: "alibaba-token-plan/qwen3.8-max-preview:medium",
|
|
262
|
+
executor: "alibaba-token-plan/deepseek-v4-pro:xhigh",
|
|
263
|
+
planner: "alibaba-token-plan/glm-5.2:high",
|
|
264
|
+
architect: "alibaba-token-plan/qwen3.8-max-preview:xhigh",
|
|
265
|
+
critic: "alibaba-token-plan/glm-5.2:high",
|
|
266
|
+
}),
|
|
267
|
+
profile("alibaba-token-plan-qwenmaxxing", ["alibaba-token-plan"], {
|
|
268
|
+
default: "alibaba-token-plan/qwen3.8-max-preview:medium",
|
|
269
|
+
executor: "alibaba-token-plan/qwen3.8-max-preview:low",
|
|
270
|
+
planner: "alibaba-token-plan/qwen3.8-max-preview:medium",
|
|
271
|
+
architect: "alibaba-token-plan/qwen3.8-max-preview:xhigh",
|
|
272
|
+
critic: "alibaba-token-plan/qwen3.8-max-preview:xhigh",
|
|
273
|
+
}),
|
|
260
274
|
profile("opus-codex", ["anthropic", "openai-codex"], {
|
|
261
275
|
default: "anthropic/claude-opus-4-8:xhigh",
|
|
262
276
|
executor: "openai-codex/gpt-5.6-terra:low",
|
|
@@ -315,6 +329,8 @@ const PROFILE_PRESENTATION: Record<string, ModelProfilePresentation> = {
|
|
|
315
329
|
"minimax-eco": { displayName: "MiniMax Eco", providerGroup: "MINIMAX" },
|
|
316
330
|
"minimax-medium": { displayName: "MiniMax Medium", providerGroup: "MINIMAX" },
|
|
317
331
|
"minimax-pro": { displayName: "MiniMax Pro", providerGroup: "MINIMAX" },
|
|
332
|
+
"alibaba-token-plan-balanced": { displayName: "Balanced", providerGroup: "ALIBABA TOKEN PLAN" },
|
|
333
|
+
"alibaba-token-plan-qwenmaxxing": { displayName: "QwenMaxxing", providerGroup: "ALIBABA TOKEN PLAN" },
|
|
318
334
|
"opus-codex": { displayName: "Opus + Codex", providerGroup: "COMBOS" },
|
|
319
335
|
"codex-opencodego": { displayName: "Codex + OpenCodeGo", providerGroup: "COMBOS" },
|
|
320
336
|
"fable-opus-codex": { displayName: "Fable + Opus + Codex", providerGroup: "COMBOS" },
|
|
@@ -330,6 +346,7 @@ const PROFILE_GROUP_ORDER = [
|
|
|
330
346
|
"GROK",
|
|
331
347
|
"CURSOR",
|
|
332
348
|
"MINIMAX",
|
|
349
|
+
"ALIBABA TOKEN PLAN",
|
|
333
350
|
"COMBOS",
|
|
334
351
|
];
|
|
335
352
|
|
|
@@ -347,6 +364,7 @@ const PROFILE_RECOMMENDATIONS: Record<string, string> = {
|
|
|
347
364
|
"grok-build": "grok-build-pro",
|
|
348
365
|
cursor: "cursor-medium",
|
|
349
366
|
"minimax-code": "minimax-medium",
|
|
367
|
+
"alibaba-token-plan": "alibaba-token-plan-balanced",
|
|
350
368
|
};
|
|
351
369
|
|
|
352
370
|
export function getModelProfilePresentation(
|
|
@@ -2049,7 +2049,10 @@ export class ModelRegistry {
|
|
|
2049
2049
|
const modelsUrl = `${baseUrl}/models`;
|
|
2050
2050
|
|
|
2051
2051
|
const headers: Record<string, string> = { ...(providerConfig.headers ?? {}) };
|
|
2052
|
-
|
|
2052
|
+
// Resolve with the same baseUrl context completion requests use so an
|
|
2053
|
+
// endpoint-scoped (or config-pinned) credential wins here exactly as it
|
|
2054
|
+
// does for chat completions.
|
|
2055
|
+
const apiKey = await this.authStorage.getApiKey(providerConfig.provider, undefined, { baseUrl });
|
|
2053
2056
|
if (apiKey && apiKey !== DEFAULT_LOCAL_TOKEN && apiKey !== kNoAuth) {
|
|
2054
2057
|
headers.Authorization = `Bearer ${apiKey}`;
|
|
2055
2058
|
}
|
|
@@ -2059,6 +2062,13 @@ export class ModelRegistry {
|
|
|
2059
2062
|
signal: AbortSignal.timeout(250),
|
|
2060
2063
|
});
|
|
2061
2064
|
if (!response.ok) {
|
|
2065
|
+
if (response.status === 401 || response.status === 403) {
|
|
2066
|
+
// Redacted by construction: name the provider, endpoint, and the
|
|
2067
|
+
// config surface to fix — never the resolved key.
|
|
2068
|
+
throw new Error(
|
|
2069
|
+
`HTTP ${response.status} from ${modelsUrl}: provider "${providerConfig.provider}" credential was rejected for OpenAI models-list discovery; check providers.${providerConfig.provider}.apiKey/apiKeyEnv.`,
|
|
2070
|
+
);
|
|
2071
|
+
}
|
|
2062
2072
|
throw new Error(`HTTP ${response.status} from ${modelsUrl}`);
|
|
2063
2073
|
}
|
|
2064
2074
|
const payload = (await response.json()) as { data?: Array<{ id: string }> };
|
|
@@ -819,6 +819,15 @@ export const SETTINGS_SCHEMA = {
|
|
|
819
819
|
type: "boolean",
|
|
820
820
|
default: true,
|
|
821
821
|
},
|
|
822
|
+
"statusLine.showActionHints": {
|
|
823
|
+
type: "boolean",
|
|
824
|
+
default: true,
|
|
825
|
+
ui: {
|
|
826
|
+
tab: "appearance",
|
|
827
|
+
label: "Status Line Action Hints",
|
|
828
|
+
description: "Show contextual keyboard shortcuts in the status line",
|
|
829
|
+
},
|
|
830
|
+
},
|
|
822
831
|
|
|
823
832
|
"statusLine.leftSegments": { type: "array", default: [] as StatusLineSegmentId[] },
|
|
824
833
|
|
|
@@ -1334,6 +1343,16 @@ export const SETTINGS_SCHEMA = {
|
|
|
1334
1343
|
description: "Suggest emojis from `:name:` shortcodes and expand text emoticons like `:D` or `:-)`",
|
|
1335
1344
|
},
|
|
1336
1345
|
},
|
|
1346
|
+
promptSuggestions: {
|
|
1347
|
+
type: "boolean",
|
|
1348
|
+
default: false,
|
|
1349
|
+
ui: {
|
|
1350
|
+
tab: "interaction",
|
|
1351
|
+
label: "Prompt Suggestions",
|
|
1352
|
+
description:
|
|
1353
|
+
"Predict your likely next prompt after each turn (smol-model call) and show it as ghost text; Tab accepts",
|
|
1354
|
+
},
|
|
1355
|
+
},
|
|
1337
1356
|
|
|
1338
1357
|
"startup.quiet": {
|
|
1339
1358
|
type: "boolean",
|
|
@@ -2451,9 +2470,9 @@ export const SETTINGS_SCHEMA = {
|
|
|
2451
2470
|
default: false,
|
|
2452
2471
|
ui: {
|
|
2453
2472
|
tab: "tools",
|
|
2454
|
-
label: "Insane Search Fallback",
|
|
2473
|
+
label: "Insane Search Fallback (Compatibility)",
|
|
2455
2474
|
description:
|
|
2456
|
-
"
|
|
2475
|
+
"Compatibility-only preference. Remote renderer fallback stays disabled because it cannot preserve validated per-hop network routing.",
|
|
2457
2476
|
},
|
|
2458
2477
|
},
|
|
2459
2478
|
|
|
@@ -3290,25 +3309,71 @@ export const SETTINGS_SCHEMA = {
|
|
|
3290
3309
|
},
|
|
3291
3310
|
"providers.image": {
|
|
3292
3311
|
type: "enum",
|
|
3293
|
-
values: ["auto", "openai", "gemini", "openrouter", "antigravity"] as const,
|
|
3312
|
+
values: ["auto", "openai", "gemini", "openrouter", "antigravity", "alibaba", "custom"] as const,
|
|
3294
3313
|
default: "auto",
|
|
3295
3314
|
ui: {
|
|
3296
3315
|
tab: "providers",
|
|
3297
|
-
label: "Image
|
|
3298
|
-
description: "Provider for image generation tool",
|
|
3316
|
+
label: "Image Generation",
|
|
3317
|
+
description: "Provider and model for image generation tool",
|
|
3299
3318
|
options: [
|
|
3300
3319
|
{
|
|
3301
3320
|
value: "auto",
|
|
3302
3321
|
label: "Auto",
|
|
3303
|
-
description: "Priority: GPT model image tool > Antigravity > OpenRouter > Gemini",
|
|
3322
|
+
description: "Priority: GPT model image tool > Antigravity > OpenRouter > Gemini > Alibaba",
|
|
3304
3323
|
},
|
|
3305
|
-
{ value: "openai", label: "OpenAI", description: "Uses
|
|
3324
|
+
{ value: "openai", label: "OpenAI", description: "Uses gpt-image-2 via OpenAI Responses/Codex" },
|
|
3306
3325
|
{ value: "gemini", label: "Gemini", description: "Requires GEMINI_API_KEY" },
|
|
3307
3326
|
{ value: "openrouter", label: "OpenRouter", description: "Requires OPENROUTER_API_KEY" },
|
|
3308
3327
|
{ value: "antigravity", label: "Antigravity", description: "Requires login with google-antigravity" },
|
|
3328
|
+
{
|
|
3329
|
+
value: "alibaba",
|
|
3330
|
+
label: "Alibaba Bailian",
|
|
3331
|
+
description: "Requires ALIBABA_TOKEN_PLAN_API_KEY (wan2.7-image via Token Plan)",
|
|
3332
|
+
},
|
|
3333
|
+
{
|
|
3334
|
+
value: "custom",
|
|
3335
|
+
label: "Custom",
|
|
3336
|
+
description: "OpenAI-compatible endpoint (set providers.imageCustomUrl)",
|
|
3337
|
+
},
|
|
3309
3338
|
],
|
|
3310
3339
|
},
|
|
3311
3340
|
},
|
|
3341
|
+
"providers.imageModel": {
|
|
3342
|
+
type: "string",
|
|
3343
|
+
default: undefined,
|
|
3344
|
+
ui: {
|
|
3345
|
+
tab: "providers",
|
|
3346
|
+
label: "Image Model",
|
|
3347
|
+
description: "Override the default image generation model for the selected provider",
|
|
3348
|
+
},
|
|
3349
|
+
},
|
|
3350
|
+
"providers.imageCustomUrl": {
|
|
3351
|
+
type: "string",
|
|
3352
|
+
default: undefined,
|
|
3353
|
+
ui: {
|
|
3354
|
+
tab: "providers",
|
|
3355
|
+
label: "Image Custom URL",
|
|
3356
|
+
description: "Base URL for custom OpenAI-compatible image endpoint",
|
|
3357
|
+
},
|
|
3358
|
+
},
|
|
3359
|
+
"providers.imageCustomKey": {
|
|
3360
|
+
type: "string",
|
|
3361
|
+
default: undefined,
|
|
3362
|
+
ui: {
|
|
3363
|
+
tab: "providers",
|
|
3364
|
+
label: "Image Custom API Key",
|
|
3365
|
+
description: "API key for custom OpenAI-compatible image endpoint",
|
|
3366
|
+
},
|
|
3367
|
+
},
|
|
3368
|
+
"providers.imageCustomKeyEnv": {
|
|
3369
|
+
type: "string",
|
|
3370
|
+
default: undefined,
|
|
3371
|
+
ui: {
|
|
3372
|
+
tab: "providers",
|
|
3373
|
+
label: "Image Custom API Key Env",
|
|
3374
|
+
description: "Environment variable name holding the API key for custom image endpoint",
|
|
3375
|
+
},
|
|
3376
|
+
},
|
|
3312
3377
|
|
|
3313
3378
|
"providers.kimiApiFormat": {
|
|
3314
3379
|
type: "enum",
|
|
@@ -3740,6 +3805,7 @@ export interface StatusLineSettings {
|
|
|
3740
3805
|
maxRows: number;
|
|
3741
3806
|
showHookStatus: boolean;
|
|
3742
3807
|
showSkillHud: boolean;
|
|
3808
|
+
showActionHints: boolean;
|
|
3743
3809
|
leftSegments: StatusLineSegmentId[];
|
|
3744
3810
|
rightSegments: StatusLineSegmentId[];
|
|
3745
3811
|
segmentOptions: Record<string, unknown>;
|
|
@@ -1,44 +1,10 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { getAgentDir } from "@gajae-code/utils";
|
|
3
3
|
import { YAML } from "bun";
|
|
4
|
+
import { UnknownModelProfileError, validateModelProfileName } from "../config/model-profile-contract";
|
|
5
|
+
import { mergeModelProfiles } from "../config/model-profiles";
|
|
4
6
|
import { ModelsConfigSchema } from "../config/models-config-schema";
|
|
5
7
|
|
|
6
|
-
/**
|
|
7
|
-
* The coordinator runs in the shipped MCP process and must stay outside the
|
|
8
|
-
* session host import graph. Keep this identity catalog in the coordinator
|
|
9
|
-
* boundary; profile activation and model registry code belong to that host.
|
|
10
|
-
*/
|
|
11
|
-
const BUILTIN_MODEL_PROFILE_NAMES = [
|
|
12
|
-
"codex-eco",
|
|
13
|
-
"codex-medium",
|
|
14
|
-
"codex-pro",
|
|
15
|
-
"opencodego",
|
|
16
|
-
"claude-opus",
|
|
17
|
-
"claude-fable",
|
|
18
|
-
"glm-eco",
|
|
19
|
-
"glm-medium",
|
|
20
|
-
"glm-pro",
|
|
21
|
-
"kimi-coding-plan-eco",
|
|
22
|
-
"kimi-coding-plan-medium",
|
|
23
|
-
"kimi-coding-plan-pro",
|
|
24
|
-
"mimo-eco",
|
|
25
|
-
"mimo-medium",
|
|
26
|
-
"mimo-pro",
|
|
27
|
-
"grok-eco",
|
|
28
|
-
"grok-medium",
|
|
29
|
-
"grok-pro",
|
|
30
|
-
"grok-build-pro",
|
|
31
|
-
"cursor-eco",
|
|
32
|
-
"cursor-medium",
|
|
33
|
-
"cursor-pro",
|
|
34
|
-
"minimax-eco",
|
|
35
|
-
"minimax-medium",
|
|
36
|
-
"minimax-pro",
|
|
37
|
-
"opus-codex",
|
|
38
|
-
"codex-opencodego",
|
|
39
|
-
"fable-opus-codex",
|
|
40
|
-
] as const;
|
|
41
|
-
|
|
42
8
|
export interface CoordinatorModelProfile {
|
|
43
9
|
name: string;
|
|
44
10
|
}
|
|
@@ -53,8 +19,6 @@ export type CoordinatorModelProfileLoader = () =>
|
|
|
53
19
|
| Promise<Map<string, CoordinatorModelProfile>>;
|
|
54
20
|
|
|
55
21
|
const MAX_ECHOED_MPRESET_LENGTH = 128;
|
|
56
|
-
const LEGACY_MODEL_PROFILE_ALIASES: ReadonlyMap<string, string> = new Map([["codex-standard", "codex-medium"]]);
|
|
57
|
-
|
|
58
22
|
/**
|
|
59
23
|
* Thrown by the default loader when `models.yml` exists but is invalid or
|
|
60
24
|
* unreadable. This lets the resolver fail closed with a distinct, stable reason
|
|
@@ -69,35 +33,25 @@ export class CoordinatorModelProfileRegistryError extends Error {
|
|
|
69
33
|
}
|
|
70
34
|
}
|
|
71
35
|
|
|
72
|
-
function
|
|
73
|
-
|
|
36
|
+
function coordinatorModelProfiles(
|
|
37
|
+
profiles?: Parameters<typeof mergeModelProfiles>[0],
|
|
38
|
+
): Map<string, CoordinatorModelProfile> {
|
|
39
|
+
return new Map([...mergeModelProfiles(profiles).keys()].map(name => [name, { name }]));
|
|
74
40
|
}
|
|
75
41
|
|
|
76
42
|
export const loadCoordinatorModelProfiles: CoordinatorModelProfileLoader = async () => {
|
|
77
43
|
const modelsFile = Bun.file(path.join(getAgentDir(), "models.yml"));
|
|
78
|
-
if (!(await modelsFile.exists())) return
|
|
44
|
+
if (!(await modelsFile.exists())) return coordinatorModelProfiles();
|
|
79
45
|
try {
|
|
80
46
|
const parsed = YAML.parse(await modelsFile.text());
|
|
81
47
|
const config = ModelsConfigSchema.safeParse(parsed);
|
|
82
48
|
if (!config.success) throw config.error;
|
|
83
|
-
|
|
84
|
-
for (const name of Object.keys(config.data.profiles ?? {})) profiles.set(name, { name });
|
|
85
|
-
return profiles;
|
|
49
|
+
return coordinatorModelProfiles(config.data.profiles);
|
|
86
50
|
} catch (error) {
|
|
87
51
|
throw new CoordinatorModelProfileRegistryError(error);
|
|
88
52
|
}
|
|
89
53
|
};
|
|
90
54
|
|
|
91
|
-
function sortedProfileNames(profiles: ReadonlyMap<string, CoordinatorModelProfile>): string[] {
|
|
92
|
-
return [...profiles.keys()].sort((left, right) => left.localeCompare(right));
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function resolveCoordinatorModelProfileName(profileName: string, profiles: ReadonlyMap<string, unknown>): string {
|
|
96
|
-
if (profiles.has(profileName)) return profileName;
|
|
97
|
-
const replacement = LEGACY_MODEL_PROFILE_ALIASES.get(profileName);
|
|
98
|
-
return replacement && profiles.has(replacement) ? replacement : profileName;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
55
|
export type CoordinatorMpresetResolution =
|
|
102
56
|
| { ok: true; mpreset: string | null }
|
|
103
57
|
| { ok: false; reason: "unknown_model_profile"; mpreset: string; available_profiles: string[] }
|
|
@@ -120,7 +74,7 @@ export async function resolveCoordinatorMpreset(
|
|
|
120
74
|
loadProfiles: CoordinatorModelProfileLoader,
|
|
121
75
|
): Promise<CoordinatorMpresetResolution> {
|
|
122
76
|
if (raw === undefined || raw === null) return { ok: true, mpreset: null };
|
|
123
|
-
const requested = typeof raw === "string" ? raw
|
|
77
|
+
const requested = typeof raw === "string" ? raw : "";
|
|
124
78
|
const echoed = requested.slice(0, MAX_ECHOED_MPRESET_LENGTH);
|
|
125
79
|
let profiles: Map<string, CoordinatorModelProfile>;
|
|
126
80
|
try {
|
|
@@ -131,24 +85,17 @@ export async function resolveCoordinatorMpreset(
|
|
|
131
85
|
}
|
|
132
86
|
throw error;
|
|
133
87
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return {
|
|
147
|
-
ok: false,
|
|
148
|
-
reason: "unknown_model_profile",
|
|
149
|
-
mpreset: echoed,
|
|
150
|
-
available_profiles: sortedProfileNames(profiles),
|
|
151
|
-
};
|
|
88
|
+
try {
|
|
89
|
+
const canonical = validateModelProfileName(requested, profiles);
|
|
90
|
+
return { ok: true, mpreset: canonical };
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (error instanceof UnknownModelProfileError)
|
|
93
|
+
return {
|
|
94
|
+
ok: false,
|
|
95
|
+
reason: "unknown_model_profile",
|
|
96
|
+
mpreset: error.details.requestedProfile.slice(0, MAX_ECHOED_MPRESET_LENGTH),
|
|
97
|
+
available_profiles: error.details.availableProfiles,
|
|
98
|
+
};
|
|
99
|
+
throw error;
|
|
152
100
|
}
|
|
153
|
-
return { ok: true, mpreset: canonical };
|
|
154
101
|
}
|
|
@@ -382,7 +382,7 @@ Auto-answer has a clarity cap: unless the architect confidence is `high` and unc
|
|
|
382
382
|
|
|
383
383
|
### Step 2b″: Refine Free-Text Answers
|
|
384
384
|
|
|
385
|
-
When the user's answer is free-text that carries reasoning, constraints, or scope decisions, do not forward it to scoring as a lossy one-line label. First structure it into a compact interpretation using the canonical sections — **Decision**, **Reasoning**, **Constraints (user-stated)**, **Out of scope (user-stated)**, and **Codebase context (verified)** (omit empty sections)
|
|
385
|
+
When the user's answer is free-text that carries reasoning, constraints, or scope decisions, do not forward it to scoring as a lossy one-line label. First structure it into a compact interpretation using the canonical sections — **Decision**, **Reasoning**, **Constraints (user-stated)**, **Out of scope (user-stated)**, and **Codebase context (verified)** (omit empty sections). Then confirm with exactly one `ask` that nothing is lost or misrepresented: the `ask` question body MUST render the full structured interpretation — every non-empty canonical section, verbatim — before the confirmation prompt. The user is approving that specific interpretation, so it must be visible inside the question body; never ask "does this capture it?" / "이 해석이 맞아?" without first displaying the interpretation itself. A confirmation `ask` whose body omits the interpretation it is asking about is a hard error: re-issue it with the interpretation shown. Apply `language.instruction` when present.
|
|
386
386
|
|
|
387
387
|
Offer options such as **Send as-is**, **Add a constraint**, **Mark something out of scope**, **Add context**, and **Rewrite**, plus free-text. If the user picks anything other than "Send as-is", collect the exact missing text with one follow-up `ask` (never infer it from the option label), fold it into the structured interpretation, and re-confirm. Do not advance to scoring while the user is still saying something is missing.
|
|
388
388
|
|
|
@@ -601,7 +601,7 @@ When ambiguity ≤ threshold (or hard cap / early exit):
|
|
|
601
601
|
|
|
602
602
|
**4a. Closure / Acceptance Guard.** Even when ambiguity ≤ threshold, do not treat the math as completion. Run an independent readiness audit from the full main-session perspective (including explore findings, established facts, and triggers the scorer may not have fully weighed). Confirm every active topology component has goal/constraint/criteria coverage, no unresolved or disputed trigger remains on a path that matters, no disputed established fact lacks a `superseded_by` resolution, and no low-confidence auto-answer is standing in for user-confirmed truth above the clarity cap. If a material gap exists, explicitly override the gate to the user — "The math says ready, but I am not accepting it yet because {gap}" — and ask the single highest-impact follow-up, returning to Phase 2. Record any override in `state.closure_overrides`.
|
|
603
603
|
|
|
604
|
-
**4b. Restate gate.** Once closure passes, collapse the agreed answers into ONE sentence goal that covers every active component, and confirm it with a single `ask
|
|
604
|
+
**4b. Restate gate.** Once closure passes, collapse the agreed answers into ONE sentence goal that covers every active component, and confirm it with a single `ask` whose body MUST begin by stating that one-sentence goal verbatim, followed by: "If someone read only this line, would they reach the same outcome you have in mind?" The goal line must be visible inside the `ask` body; never ask the confirmation without first displaying the collapsed goal it refers to. Offer **Yes, crystallize**, **Adjust wording**, and **Missing scope**, plus free-text, applying `language.instruction` when present. Because this gate has options, it MUST go through `ask`: do not print the Restate question and options as assistant prose with `Question:`/`Options:` labels. If the Restate gate was already printed that way, immediately call `ask` with the same question/options before accepting or waiting for any answer. On "Adjust wording" / "Missing scope", collect the exact correction with one follow-up `ask`, route it back through Step 2c scoring and established-facts maintenance (a correction can change ambiguity), then re-run closure and ask the Restate gate again. Cap at two loops; if alignment is not reached, return to Phase 2 with a targeted question instead of forcing a goal line. Persist the confirmed line as `state.restated_goal`.
|
|
605
605
|
|
|
606
606
|
1. **Generate the specification** using opus model with the prompt-safe transcript. If the full interview transcript or initial context is too large, include the summary plus all concrete decisions, acceptance criteria, unresolved gaps, and ontology snapshots; never overflow the prompt with raw oversized context.
|
|
607
607
|
- Apply `language.instruction` when present so user-facing prose in the spec preserves the session language; keep code identifiers, file paths, commands, JSON/settings keys, and quoted source text unchanged.
|
|
@@ -34,6 +34,8 @@ For corrupt, tampered, unreadable, or stale current-session ralplan state, run `
|
|
|
34
34
|
|
|
35
35
|
Ralplan is planning only. It may inspect context and draft plan/spec/proposal artifacts, but those remain `pending approval` until explicit current-turn or structured-UI execution approval. Before that approval, do not mutate product source, run mutation-oriented shell, commit, push, open PRs, invoke execution skills, or delegate implementation.
|
|
36
36
|
|
|
37
|
+
Explicitly naming `ultragoal` or `team` (including `/skill:` and `gjc` forms) counts as opting into execution for that skill — do not re-ask for the same consent.
|
|
38
|
+
|
|
37
39
|
Persist planning artifacts and handoffs through the ralplan CLI writer, never direct `.gjc/` edits:
|
|
38
40
|
Direct `write`, `edit`, or `ast_edit` calls against `.gjc/_session-{sessionid}/specs`, `.gjc/_session-{sessionid}/plans`, `.gjc/_session-{sessionid}/state`, or any other `.gjc/` path are forbidden unless an explicit force override is active.
|
|
39
41
|
|
|
@@ -82,7 +84,7 @@ The consensus workflow:
|
|
|
82
84
|
- For every confirmed open item, embed the resolved outcome into the final plan under an **## Intent Reconciliation** section so the `pending approval` artifact records each decision; record any item the user explicitly defers as an open confirmation under that same section.
|
|
83
85
|
d. Persist the reconciliation with `gjc ralplan --write --stage post-interview --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json`, then return the receipt/path plus a compact status (reconciled-clean / reconciled-with-revision / open-confirmations-pending) instead of pasting the full body.
|
|
84
86
|
7. On reconciliation completion, re-check the review join gate (Critic `OKAY` plus Architect `CLEAR`/`APPROVE` for the same Planner artifact/pass), mark the plan `pending approval` unless explicit execution approval has already been captured, persist the ADR/final plan via `gjc ralplan --write --stage final --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT`, and do not directly edit `.gjc/_session-{sessionid}/plans`. Final plan must include ADR (Decision, Drivers, Alternatives considered, Why chosen, Consequences, Follow-ups) and, when present, the **## Intent Reconciliation** section.
|
|
85
|
-
8. **
|
|
87
|
+
8. **Final approval gate (with explicit-execution exception):** If the user already explicitly named an execution skill in the current turn or via the structured approval UI (`ultragoal`, `/skill:ultragoal`, `gjc ultragoal`, `team`, `/skill:team`, `gjc team`, or "Approve execution via ultragoal/team"), that is execution approval — skip the re-ask and proceed to step 9 with that skill. Otherwise, **always** present the finalized plan via the `ask` tool (regardless of `--interactive`) with `workflowGate: { stage: "ralplan", kind: "approval" }` on the final question so RPC/headless clients receive a `ralplan`/`approval` workflow gate, not a deep-interview question gate. Use these options:
|
|
86
88
|
- **Refine further** — re-run the consensus loop / request changes, then return here
|
|
87
89
|
- **Approve execution via ultragoal (Recommended)** — goal-tracked autonomous execution
|
|
88
90
|
- **Approve execution via team** — only when tmux-based interactive worker parallelization is required
|
|
@@ -123,10 +123,12 @@ An active Ultragoal run must not give up on a blocker by pausing the goal and as
|
|
|
123
123
|
- **`resolvable`** — anything the agent can act on: failing tests, missing implementation, a dependency to install, an ambiguous-but-inferable detail, investigation. **Never pause.** Exhaust autonomous resolution first: investigate, `gjc ultragoal steer --kind add_subgoal --title "Investigate blocker" --objective "..." --evidence "..." --rationale "..."`, delegate an `executor`, or preserve the blocker durably with `gjc ultragoal checkpoint --status blocked` / `gjc ultragoal record-review-blockers` and keep scheduling the next goal.
|
|
124
124
|
- **`human_blocked`** — only the user can act: credentials/secrets, a manual or physical step, an external approval/decision, access the agent lacks. Pause is the last resort and is gated.
|
|
125
125
|
|
|
126
|
-
`goal({"op":"pause"})` is **blocked at runtime** while an Ultragoal run is active unless the latest
|
|
126
|
+
`goal({"op":"pause"})` is **blocked at runtime** while an Ultragoal run is active unless the latest `blocker_classified` ledger event is `human_blocked` and a later bound clean pause terminal critic verdict is recorded for it (see [Terminal critic gate](#terminal-critic-gate)). `assertUltragoalPauseAllowed` first consumes a pre-existing give-up nudge (a durable ledger write) before it runs the read-only pause diagnostic; only `isUltragoalPauseBlocked` is a pure reader. To pause, first record the human-only classification and capture its event id, then record the terminal critic's clean bound pause verdict, and only then pause:
|
|
127
127
|
|
|
128
128
|
```sh
|
|
129
129
|
gjc ultragoal classify-blocker --classification human_blocked --evidence "<the specific human-only dependency>" [--goal-id <id>]
|
|
130
|
+
gjc ultragoal record-critic-verdict --terminus pause --classification-event-id <eventId> --verdict OKAY --evidence "<terminal critic evidence>"
|
|
131
|
+
goal({"op":"pause"})
|
|
130
132
|
```
|
|
131
133
|
|
|
132
134
|
Recording `--classification resolvable` is an audit note only; it never authorizes a pause. The `ask` tool stays blocked during active runs regardless of classification — record unresolved decisions as durable blockers instead of prompting.
|
|
@@ -343,6 +345,44 @@ Provide one `artifactRefs` entry per live surface actually exercised, using the
|
|
|
343
345
|
|
|
344
346
|
For CLI replay artifacts, the JSON at `path` must be an object like `{"schemaVersion":1,"kind":"cli-replay","replaySafe":true,"command":["bun","-e","console.log(\"ultragoal-cli-ok\")"],"cwd":".","env":{"LC_ALL":"C"},"timeoutMs":30000,"expectedExitCode":0,"recordedStdout":"ultragoal-cli-ok\n","recordedStderr":"","invariants":[{"type":"substring","value":"ultragoal-cli-ok"},{"type":"not-substring","value":"error"}]}`. Accepted replay fields are `command` (string array), optional `cwd`, safe `env`, `timeoutMs`, `expectedExitCode`, `recordedStdout`, `recordedStderr`, `normalization`, and `invariants`. The conservative command allowlist is intentionally small: `bun --version`, `node --version`, deterministic `bun/node -e "console.log(...)"`, `npm|pnpm|yarn --version`, `npm|pnpm|yarn list`, read-only `git status|rev-parse|merge-base|diff|show|log` with safe args, and `gjc read|status`. `env` must contain only safe deterministic variables, never credentials or machine/user-specific secrets. `normalization` is optional and, when provided, must be exactly the string `"default"` (the built-in normalizer already strips ANSI codes, normalizes line endings, scrubs paths, and trims trailing whitespace); object-shaped normalization is rejected. Invariants may be substring, regex, or not-substring checks; when present, they replace exact `recordedStdout` equality — without `invariants`, replayed normalized stdout must match `recordedStdout` exactly. Unsafe, non-deterministic, credentialed, interactive, or otherwise unallowlisted commands require audited `replayExempt` metadata with exact fields `reasonCode`, `reason`, `approvedBy`, and `fallbackArtifactRefs` plus a structurally valid same-surface fallback artifact. `reason` must be substantive and audited, and `approvedBy` must identify the verifier. Allowed `reasonCode` values are exactly `unsafe_side_effect`, `requires_credentials`, `requires_network`, `non_deterministic_external`, `destructive`, `interactive_only`, and `platform_unavailable`.
|
|
345
347
|
|
|
348
|
+
## Terminal critic gate
|
|
349
|
+
|
|
350
|
+
The terminal critic gate is a fail-closed, once-per-run-terminus review. It guards both terminal exits with a read-only `critic` role agent's `OKAY` verdict; it does not run per story. It is additive to, and does not change, the existing per-story `architect` review and `executor` QA/red-team lanes.
|
|
351
|
+
|
|
352
|
+
### Completion terminus
|
|
353
|
+
|
|
354
|
+
Before assembling the final-aggregate `--quality-gate-json`, the leader delegates the terminal critic. Only the final-aggregate completion checkpoint requires the additional top-level `criticReview` key; `criticReview` is tolerated but ignored on non-final checkpoints. A clean final aggregate requires `verdict: "OKAY"`, non-empty `evidence`, and an empty `blockers` array:
|
|
355
|
+
|
|
356
|
+
```json
|
|
357
|
+
{
|
|
358
|
+
"criticReview": {
|
|
359
|
+
"verdict": "OKAY",
|
|
360
|
+
"evidence": "terminal critic review of the final required-goal state",
|
|
361
|
+
"blockers": []
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Pause/blocked terminus
|
|
367
|
+
|
|
368
|
+
At a `human_blocked` terminus, the leader first runs `gjc ultragoal classify-blocker --classification human_blocked` (capturing that classification's ledger `eventId`), then delegates the terminal critic and records its verdict with `gjc ultragoal record-critic-verdict --terminus pause --classification-event-id <eventId>` before calling `goal({"op":"pause"})`. The pause is allowed only when a later fresh `critic_verdict` ledger receipt exists with `terminus: "pause"`, `verdict: "OKAY"`, non-empty evidence, an empty blockers array, the current `planGeneration`, and a `classificationEventId` bound to the latest `blocker_classified` event, which must be `human_blocked`. Freshness is scoped to the final required-goal state, so required-goal or steer changes stale the receipt, and a newer classification supersedes an older verdict.
|
|
369
|
+
|
|
370
|
+
The critic must verify that the `human_blocked` classification is genuine, including catching false pauses where needed resources exist locally or the asserted blocker is resolvable. A `REJECT` (or `ITERATE`) verdict refuses the terminal pause; the run keeps executing. The pause (`goal({"op":"pause"})`) is the gated terminal park-and-wait exit — a per-goal `gjc ultragoal checkpoint --status blocked` remains available as non-terminal blocker bookkeeping that never signals run completion and keeps the blocker outstanding until resolved.
|
|
371
|
+
|
|
372
|
+
### Invocation and containment
|
|
373
|
+
|
|
374
|
+
At each terminus, the leader gives the read-only `critic` role agent `brief.md`, `goals.json`, `ledger.jsonl`, and the cumulative change set. For completion, invoke it before assembling the final-aggregate gate JSON. For pause, invoke it after the `human_blocked` classification and before `goal({"op":"pause"})`. The terminal critic must not spawn nested `ralplan`, `team`, `deep-interview`, or `ultragoal` workflows. This creates no interactive surface: `ask` remains blocked while an Ultragoal run is active.
|
|
375
|
+
|
|
376
|
+
### Non-OKAY loop and ceiling
|
|
377
|
+
|
|
378
|
+
For completion-side `ITERATE` or `REJECT`, the leader MUST first record the terminal verdict so the run-level counter observes it: `gjc ultragoal record-critic-verdict --terminus completion --verdict <ITERATE|REJECT> --evidence "<critic findings>"`; then record the findings with `gjc ultragoal record-review-blockers` and reopen the run. The dedicated counter ceiling is 5, independently of the give-up nudge budget, and is **RUN-LEVEL**: it counts every non-OKAY terminal-critic verdict across the whole run and all reopen cycles. On reaching that ceiling, both pause and final completion are blocked until a human or leader records `gjc ultragoal record-critic-gate-override --evidence "<authorization evidence>"`. There is no automatic pause override.
|
|
379
|
+
|
|
380
|
+
This gate is always fail-closed and has no grandfathering: in-flight runs must obtain a terminal verdict when they reach a terminus.
|
|
381
|
+
|
|
382
|
+
#### Deferred / out of scope
|
|
383
|
+
|
|
384
|
+
Gating active-aggregate `goal drop` after nudge exhaustion is a known follow-up not covered by this gate; `drop` remains governed by the existing nudge discipline.
|
|
385
|
+
|
|
346
386
|
## Review mode
|
|
347
387
|
|
|
348
388
|
`gjc ultragoal review` runs the same hardened gate against an already implemented PR, branch, or worktree. Use `--pr <number>` for a PR, `--branch <ref>` for a branch diff, omit both for the current worktree, and pass `--spec <path>` when a real contract exists. `--mode review-only` emits the verdict/findings without creating fix work; `--mode review-start` records review blockers for follow-up. Review mode validates the same `executorQa` shape and live-surface artifacts as `checkpoint --status complete`. A thin or derived-only contract can never clean-pass: the verdict is capped at `inconclusive: weak-contract` until a supplied spec or equivalent strong acceptance criteria are available.
|
|
@@ -372,4 +412,5 @@ The skill tool then dispatches `/skill:ralplan` or `/skill:deep-interview` same-
|
|
|
372
412
|
- Never call `goal({"op":"complete"})` unless the aggregate run or legacy per-story goal is actually complete.
|
|
373
413
|
- In aggregate mode, intermediate and final story checkpoints update durable `goals.json` state and append receipt proof to `ledger.jsonl`; the final story checkpoint creates the final aggregate receipt before the agent may call `goal({"op":"complete"})`.
|
|
374
414
|
- Completion checkpoints require `--quality-gate-json` only. Shell commands and hooks must not mutate goal state; the agent reconciles inline goal-tool state after durable completion.
|
|
415
|
+
- Final-aggregate completion additionally requires a `criticReview` `OKAY`; a `human_blocked` pause additionally requires a fresh `OKAY` `critic_verdict` receipt.
|
|
375
416
|
- Treat `ledger.jsonl` as the durable audit trail; checkpoint after every success or failure.
|