@gajae-code/coding-agent 0.11.10 → 0.11.11
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 +38 -0
- package/dist/types/cli.d.ts +8 -1
- package/dist/types/config/model-profile-activation.d.ts +1 -0
- package/dist/types/config/model-profile-contract.d.ts +2 -0
- package/dist/types/config/settings-schema.d.ts +57 -2
- package/dist/types/config/settings.d.ts +1 -0
- package/dist/types/extensibility/extensions/types.d.ts +21 -0
- package/dist/types/gjc-runtime/linux-proc.d.ts +22 -14
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +60 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +3 -1
- package/dist/types/modes/acp/acp-agent.d.ts +14 -9
- package/dist/types/modes/components/queue-pane.d.ts +8 -0
- package/dist/types/modes/components/queued-message-selector.d.ts +6 -0
- package/dist/types/modes/components/welcome.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +5 -1
- package/dist/types/runtime/memory-domain.d.ts +9 -0
- package/dist/types/runtime/memory-guard-contract.d.ts +71 -0
- package/dist/types/runtime/memory-guard.d.ts +39 -0
- package/dist/types/runtime/memory-limit.d.ts +11 -0
- package/dist/types/runtime-mcp/manager.d.ts +8 -0
- package/dist/types/sdk/acp/final-text.d.ts +19 -0
- package/dist/types/sdk/acp/mcp.d.ts +23 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +2 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -1
- package/dist/types/sdk/bus/index.d.ts +3 -1
- package/dist/types/sdk/bus/rate-limit-pool.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +23 -3
- package/dist/types/sdk/host/control/operations.d.ts +1 -0
- package/dist/types/sdk/lifecycle-session.d.ts +10 -1
- package/dist/types/sdk/startup-capability.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +9 -11
- package/dist/types/session/fallback-chain-controller.d.ts +18 -2
- package/dist/types/session/internal/managed-session-scope.d.ts +27 -0
- package/dist/types/session/session-manager.d.ts +34 -0
- package/dist/types/task/executor.d.ts +6 -0
- package/dist/types/task/types.d.ts +36 -0
- package/dist/types/task/ultragoal-redteam-activation.d.ts +33 -0
- package/dist/types/tools/resource-gc.d.ts +19 -0
- package/package.json +8 -8
- package/scripts/generate-sdk-operation-inventory.ts +2 -0
- package/scripts/run-test-manifest.ts +6 -4
- package/src/cli/args.ts +14 -8
- package/src/cli/fast-help.ts +1 -1
- package/src/cli.ts +48 -2
- package/src/commands/sdk.ts +91 -7
- package/src/config/model-profile-activation.ts +1 -0
- package/src/config/model-profile-contract.ts +13 -0
- package/src/config/model-profiles.ts +24 -0
- package/src/config/settings-schema.ts +61 -2
- package/src/config/settings.ts +8 -0
- package/src/defaults/gjc/skills/ralplan/SKILL.md +21 -2
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +1 -1
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +14 -0
- package/src/gjc-runtime/deep-interview-ambiguity.ts +16 -2
- package/src/gjc-runtime/deep-interview-state.ts +25 -3
- package/src/gjc-runtime/linux-proc.ts +70 -36
- package/src/gjc-runtime/ralplan-runtime.ts +252 -1
- package/src/gjc-runtime/state-runtime.ts +48 -11
- package/src/internal-urls/docs-index.generated.ts +4 -4
- package/src/internal-urls/local-protocol.ts +23 -4
- package/src/modes/acp/acp-agent.ts +554 -60
- package/src/modes/acp/acp-event-mapper.ts +93 -6
- package/src/modes/components/model-selector.ts +15 -1
- package/src/modes/components/notifications-settings-editor.ts +1 -1
- package/src/modes/components/queue-pane.ts +55 -9
- package/src/modes/components/queued-message-selector.ts +53 -10
- package/src/modes/components/tool-status-header.ts +1 -4
- package/src/modes/components/welcome.ts +18 -3
- package/src/modes/controllers/command-controller.ts +4 -0
- package/src/modes/controllers/extension-ui-controller.ts +4 -10
- package/src/modes/controllers/input-controller.ts +44 -3
- package/src/modes/interactive-mode.ts +65 -26
- package/src/modes/runtime-init.ts +1 -0
- package/src/prompts/agents/executor.md +1 -1
- package/src/runtime/memory-domain.ts +66 -0
- package/src/runtime/memory-guard-contract.ts +73 -0
- package/src/runtime/memory-guard.ts +292 -0
- package/src/runtime/memory-limit.ts +50 -0
- package/src/runtime-mcp/manager.ts +26 -6
- package/src/sdk/acp/adapter.ts +8 -1
- package/src/sdk/acp/final-text.ts +40 -0
- package/src/sdk/acp/mcp.ts +26 -0
- package/src/sdk/broker/lifecycle.ts +82 -1
- package/src/sdk/bus/chat-daemon-cli.ts +2 -2
- package/src/sdk/bus/chat-daemon-control.ts +4 -3
- package/src/sdk/bus/config.ts +1 -1
- package/src/sdk/bus/index.ts +162 -22
- package/src/sdk/bus/rate-limit-pool.ts +8 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +6 -4
- package/src/sdk/bus/telegram-daemon.ts +531 -53
- package/src/sdk/host/control/dispatch.ts +2 -0
- package/src/sdk/host/control/operations.ts +1 -0
- package/src/sdk/host/host.ts +2 -2
- package/src/sdk/lifecycle-session.ts +19 -3
- package/src/sdk/protocol/operation-inventory.generated.json +47 -0
- package/src/sdk/protocol/operation-registry.ts +1 -0
- package/src/sdk/session.ts +12 -1
- package/src/sdk/startup-capability.ts +6 -0
- package/src/session/agent-session.ts +256 -142
- package/src/session/fallback-chain-controller.ts +47 -8
- package/src/session/internal/managed-session-scope.ts +40 -10
- package/src/session/internal/native-publish-outcome.ts +8 -3
- package/src/session/session-manager.ts +608 -25
- package/src/task/executor.ts +11 -3
- package/src/task/index.ts +2 -0
- package/src/task/types.ts +22 -11
- package/src/task/ultragoal-redteam-activation.ts +75 -0
- package/src/tools/resource-gc.ts +496 -120
- package/src/workflow/workflow-intent-diff.ts +4 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { WindowsJobMemoryProbeResult } from "@gajae-code/natives";
|
|
1
2
|
import type { Settings } from "../config/settings";
|
|
2
3
|
import { type TabGcSnapshot } from "./browser/tab-supervisor";
|
|
3
4
|
export interface BrowserGcPolicy {
|
|
@@ -16,7 +17,10 @@ export declare function resolveSweepIntervalMs(settings: Settings): number;
|
|
|
16
17
|
/** Injectable seams so the controller is fully testable without real browsers/filesystem/RSS. */
|
|
17
18
|
export interface ResourceGcDeps {
|
|
18
19
|
now: () => number;
|
|
20
|
+
monotonicNow: () => number;
|
|
19
21
|
rssBytes: () => number;
|
|
22
|
+
memorySnapshot: () => Promise<MemoryPressureSnapshot>;
|
|
23
|
+
runGc: () => void;
|
|
20
24
|
logWarn: (msg: string, meta?: Record<string, unknown>) => void;
|
|
21
25
|
listTabs: () => TabGcSnapshot[];
|
|
22
26
|
releaseTab: (name: string, policy: {
|
|
@@ -43,6 +47,21 @@ export interface ResourceGcRegistration {
|
|
|
43
47
|
*/
|
|
44
48
|
export declare function registerResourceGcSession(reg: ResourceGcRegistration): () => void;
|
|
45
49
|
export declare function sweepOnce(d?: ResourceGcDeps): Promise<void>;
|
|
50
|
+
export interface MemoryPressureDomain {
|
|
51
|
+
hardCapBytes: number;
|
|
52
|
+
totalUsageBytes: number;
|
|
53
|
+
source: MemoryPressureSnapshot["source"];
|
|
54
|
+
}
|
|
55
|
+
export interface MemoryPressureSnapshot {
|
|
56
|
+
hardCapBytes: number;
|
|
57
|
+
totalUsageBytes: number;
|
|
58
|
+
parentBytes: number;
|
|
59
|
+
source: "host" | "linux_cgroup_v2" | "linux_cgroup_v1" | "windows_job" | "windows_process_job_limit";
|
|
60
|
+
domains?: MemoryPressureDomain[];
|
|
61
|
+
}
|
|
62
|
+
export declare function __sampleLinuxCgroupHierarchyForTest(mountInfo: string, membership: string, fsType: "cgroup" | "cgroup2", hostBytes: number, parentBytes: number): Promise<MemoryPressureSnapshot | null>;
|
|
63
|
+
export declare function __sampleWindowsJobMemoryForTest(hostBytes: number, parentBytes: number, probeResult?: WindowsJobMemoryProbeResult): MemoryPressureSnapshot | null;
|
|
64
|
+
export declare function __selectMemoryPressureDomainForTest(snapshot: MemoryPressureSnapshot, policyLimitBytes: number | null): MemoryPressureSnapshot;
|
|
46
65
|
export declare function __setResourceGcDepsForTest(overrides: Partial<ResourceGcDeps>): void;
|
|
47
66
|
export declare function __setResourceGcSchedulerNowForTest(now: () => number): void;
|
|
48
67
|
export declare function __runResourceGcTickForTest(): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/coding-agent",
|
|
4
|
-
"version": "0.11.
|
|
4
|
+
"version": "0.11.11",
|
|
5
5
|
"description": "Gajae Code CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://gajae-code.com",
|
|
7
7
|
"author": "Yeachan-Heo and Gajae Code Contributors",
|
|
@@ -53,13 +53,13 @@
|
|
|
53
53
|
"@agentclientprotocol/sdk": "1.2.1",
|
|
54
54
|
"@babel/parser": "^7.29.3",
|
|
55
55
|
"@mozilla/readability": "^0.6.0",
|
|
56
|
-
"@gajae-code/stats": "0.11.
|
|
57
|
-
"@gajae-code/agent-core": "0.11.
|
|
58
|
-
"@gajae-code/ai": "0.11.
|
|
59
|
-
"@gajae-code/bridge-client": "0.11.
|
|
60
|
-
"@gajae-code/natives": "0.11.
|
|
61
|
-
"@gajae-code/tui": "0.11.
|
|
62
|
-
"@gajae-code/utils": "0.11.
|
|
56
|
+
"@gajae-code/stats": "0.11.11",
|
|
57
|
+
"@gajae-code/agent-core": "0.11.11",
|
|
58
|
+
"@gajae-code/ai": "0.11.11",
|
|
59
|
+
"@gajae-code/bridge-client": "0.11.11",
|
|
60
|
+
"@gajae-code/natives": "0.11.11",
|
|
61
|
+
"@gajae-code/tui": "0.11.11",
|
|
62
|
+
"@gajae-code/utils": "0.11.11",
|
|
63
63
|
"@puppeteer/browsers": "^2.13.0",
|
|
64
64
|
"@types/turndown": "5.0.6",
|
|
65
65
|
"@xterm/headless": "^6.0.0",
|
|
@@ -86,6 +86,7 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
|
|
|
86
86
|
"agent_session:refreshMCPTools": "internal accessor/plumbing, not a user-facing control seam",
|
|
87
87
|
"agent_session:refreshGjcSubskillTools": "internal accessor/plumbing, not a user-facing control seam",
|
|
88
88
|
"agent_session:buildDisplaySessionContext": "internal accessor/plumbing, not a user-facing control seam",
|
|
89
|
+
"agent_session:buildPreparedDisplaySessionContext": "internal accessor/plumbing, not a user-facing control seam",
|
|
89
90
|
"agent_session:convertMessagesToLlm": "internal accessor/plumbing, not a user-facing control seam",
|
|
90
91
|
"agent_session:prepareSimpleStreamOptions": "internal accessor/plumbing, not a user-facing control seam",
|
|
91
92
|
"agent_session:getPlanModeState": "internal accessor/plumbing, not a user-facing control seam",
|
|
@@ -198,6 +199,7 @@ const SEAM_TO_SDK: Readonly<Record<string, string>> = {
|
|
|
198
199
|
"agent_session:setSessionName": "session.rename",
|
|
199
200
|
"agent_session:setModel": "model.set",
|
|
200
201
|
"agent_session:setDefaultModelSelection": "model.set",
|
|
202
|
+
"agent_session:activateModelProfileForControl": "model.profile.set",
|
|
201
203
|
"agent_session:cycleModel": "model.cycle",
|
|
202
204
|
"agent_session:setThinkingLevel": "thinking.set",
|
|
203
205
|
"agent_session:cycleThinkingLevel": "thinking.cycle",
|
|
@@ -36,6 +36,8 @@ try {
|
|
|
36
36
|
function validateRowReceipts(rows: ManifestAdapterRow[]): void {
|
|
37
37
|
const ids = new Set<string>();
|
|
38
38
|
const counts = new Map<string, number>();
|
|
39
|
+
// Each operation has one receipt, plus the dedicated config.patch secret-input receipt.
|
|
40
|
+
const expectedRowsPerAdapter = OPERATIONS.length + 1;
|
|
39
41
|
for (const row of rows) {
|
|
40
42
|
if (ids.has(row.adapterTestId)) throw new Error(`Duplicate adapter test ID: ${row.adapterTestId}`);
|
|
41
43
|
ids.add(row.adapterTestId);
|
|
@@ -60,11 +62,11 @@ function validateRowReceipts(rows: ManifestAdapterRow[]): void {
|
|
|
60
62
|
}
|
|
61
63
|
counts.set(row.adapter, (counts.get(row.adapter) ?? 0) + 1);
|
|
62
64
|
}
|
|
63
|
-
if (rows.length !== ADAPTERS.length *
|
|
64
|
-
throw new Error(`Expected ${ADAPTERS.length *
|
|
65
|
+
if (rows.length !== ADAPTERS.length * expectedRowsPerAdapter)
|
|
66
|
+
throw new Error(`Expected ${ADAPTERS.length * expectedRowsPerAdapter} adapter rows; received ${rows.length}.`);
|
|
65
67
|
for (const adapter of ADAPTERS) {
|
|
66
|
-
if (counts.get(adapter) !==
|
|
67
|
-
throw new Error(`Expected
|
|
68
|
+
if (counts.get(adapter) !== expectedRowsPerAdapter)
|
|
69
|
+
throw new Error(`Expected ${expectedRowsPerAdapter} ${adapter} rows; received ${counts.get(adapter) ?? 0}.`);
|
|
68
70
|
}
|
|
69
71
|
process.stdout.write(
|
|
70
72
|
`manifest check: ${rows.length} row receipts (${[...counts].map(([adapter, count]) => `${adapter}=${count}`).join(", ")})\n`,
|
package/src/cli/args.ts
CHANGED
|
@@ -205,17 +205,23 @@ export function parseArgs(args: string[]): Args {
|
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
result.tools = validTools;
|
|
208
|
-
} else if (arg === "--thinking"
|
|
208
|
+
} else if (arg === "--thinking") {
|
|
209
|
+
// Match --credential / --mcp-config: a missing value or a following flag is
|
|
210
|
+
// a usage error, not a silent no-op / accidental consumption of `-p`.
|
|
211
|
+
const next = args[i + 1];
|
|
212
|
+
if (!next || next.startsWith("-")) {
|
|
213
|
+
throw new CliParseError(`--thinking requires <level> (${THINKING_EFFORTS.join(", ")})`);
|
|
214
|
+
}
|
|
209
215
|
const rawThinking = args[++i];
|
|
210
216
|
const thinking = parseEffort(rawThinking);
|
|
211
|
-
if (thinking
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
level:
|
|
216
|
-
|
|
217
|
-
});
|
|
217
|
+
if (thinking === undefined) {
|
|
218
|
+
// Fail closed: a silent ignore left users believing `ultra` (or any typo)
|
|
219
|
+
// was applied. Help / Flags.options advertise the real Effort enum.
|
|
220
|
+
throw new CliParseError(
|
|
221
|
+
`Invalid --thinking level "${rawThinking}". Expected one of: ${THINKING_EFFORTS.join(", ")}`,
|
|
222
|
+
);
|
|
218
223
|
}
|
|
224
|
+
result.thinking = thinking;
|
|
219
225
|
} else if (arg === "--print" || arg === "-p") {
|
|
220
226
|
result.print = true;
|
|
221
227
|
} else if (arg === "--export" && i + 1 < args.length) {
|
package/src/cli/fast-help.ts
CHANGED
|
@@ -84,7 +84,7 @@ Environment Variables:
|
|
|
84
84
|
GJC_LAUNCH_POLICY - Launch policy for --tmux startup: tmux or direct
|
|
85
85
|
GJC_TMUX_SESSION - Explicit tmux session name override for --tmux startup
|
|
86
86
|
GJC_TMUX_PROFILE - Apply GJC tmux scroll/mouse/clipboard profile to --tmux sessions (set 0/off to skip)
|
|
87
|
-
GJC_MOUSE -
|
|
87
|
+
GJC_MOUSE - Apply tmux copy-mode mouse capture in --tmux sessions (set 0/off to skip)
|
|
88
88
|
|
|
89
89
|
For complete environment variable reference, see:
|
|
90
90
|
docs/environment-variables.md
|
package/src/cli.ts
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
* lightweight CLI runner from pi-utils.
|
|
6
6
|
*/
|
|
7
7
|
import "@gajae-code/utils/postmortem";
|
|
8
|
+
import { THINKING_EFFORTS } from "@gajae-code/ai";
|
|
8
9
|
import { Args, type CliConfig, Command, type CommandEntry, Flags, run } from "@gajae-code/utils/cli";
|
|
9
10
|
import { APP_NAME, formatBunRuntimeError, MIN_BUN_VERSION, VERSION } from "@gajae-code/utils/dirs";
|
|
11
|
+
import { loadNative as loadNativeBindings } from "../../natives/native/loader-state.js";
|
|
10
12
|
import { runFixtureReport } from "./cli/fixture-report";
|
|
11
13
|
import { admitManagedOwnerBeforeCli, completeManagedOwnerRecovery } from "./gjc-runtime/managed-owner-admission";
|
|
12
14
|
import {
|
|
@@ -174,6 +176,46 @@ async function runChatDaemonInternalFastPath(argv: string[]): Promise<void> {
|
|
|
174
176
|
await runChatDaemonInternal(action === "discord-internal" ? "discord" : "slack", argv.slice(2));
|
|
175
177
|
}
|
|
176
178
|
|
|
179
|
+
type MemoryGuardNativeSmokeLoad = () => Record<string, unknown>;
|
|
180
|
+
type WindowsJobMemoryProbeResult = Record<string, unknown> & { kind: string };
|
|
181
|
+
type MemoryGuardNativeSmokeReceipt = {
|
|
182
|
+
api: "memory_guard_windows_job_probe_v1";
|
|
183
|
+
source: "pi_natives";
|
|
184
|
+
result: WindowsJobMemoryProbeResult;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export function isMemoryGuardNativeSmokeFastPath(argv: readonly string[]): boolean {
|
|
188
|
+
return (
|
|
189
|
+
argv.length === 3 && argv[0] === "internal" && argv[1] === "memory-guard-native-smoke" && argv[2] === "--json"
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function parseWindowsJobMemoryProbeResult(value: unknown): WindowsJobMemoryProbeResult {
|
|
194
|
+
if (!value || typeof value !== "object") {
|
|
195
|
+
throw new Error("memory-guard-native-smoke: native probe returned a non-object result");
|
|
196
|
+
}
|
|
197
|
+
const result = value as Record<string, unknown>;
|
|
198
|
+
if (typeof result.kind !== "string") {
|
|
199
|
+
throw new Error("memory-guard-native-smoke: native probe result is missing a string kind tag");
|
|
200
|
+
}
|
|
201
|
+
return result as WindowsJobMemoryProbeResult;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function runMemoryGuardNativeSmokeFastPath(
|
|
205
|
+
options: { loadNative?: MemoryGuardNativeSmokeLoad; writeStdout?: (text: string) => void } = {},
|
|
206
|
+
): void {
|
|
207
|
+
const probe = (options.loadNative ?? loadNativeBindings)().probeWindowsJobMemory;
|
|
208
|
+
if (typeof probe !== "function") {
|
|
209
|
+
throw new Error("memory-guard-native-smoke: probeWindowsJobMemory export missing from native addon");
|
|
210
|
+
}
|
|
211
|
+
const receipt: MemoryGuardNativeSmokeReceipt = {
|
|
212
|
+
api: "memory_guard_windows_job_probe_v1",
|
|
213
|
+
source: "pi_natives",
|
|
214
|
+
result: parseWindowsJobMemoryProbeResult((probe as () => unknown)()),
|
|
215
|
+
};
|
|
216
|
+
(options.writeStdout ?? (text => process.stdout.write(text)))(`${JSON.stringify(receipt)}\n`);
|
|
217
|
+
}
|
|
218
|
+
|
|
177
219
|
function rootFixtureArg(argv: string[]): { present: boolean; id: string | undefined } {
|
|
178
220
|
for (let i = 0; i < argv.length; i++) {
|
|
179
221
|
const arg = argv[i];
|
|
@@ -244,8 +286,8 @@ export class RootHelpCommand extends Command {
|
|
|
244
286
|
tmux: Flags.boolean({ description: "Launch interactive startup inside tmux" }),
|
|
245
287
|
tools: Flags.string({ description: "Comma-separated list of tools to enable (default: all)" }),
|
|
246
288
|
thinking: Flags.string({
|
|
247
|
-
description:
|
|
248
|
-
options: [
|
|
289
|
+
description: `Set thinking level: ${THINKING_EFFORTS.join(", ")}`,
|
|
290
|
+
options: [...THINKING_EFFORTS],
|
|
249
291
|
}),
|
|
250
292
|
hook: Flags.string({ description: "Load a hook/extension file (can be used multiple times)", multiple: true }),
|
|
251
293
|
extension: Flags.string({
|
|
@@ -382,6 +424,10 @@ export async function runCli(argv: string[]): Promise<void> {
|
|
|
382
424
|
}
|
|
383
425
|
// Re-exec could not be spawned; fall through and run in this process.
|
|
384
426
|
}
|
|
427
|
+
if (isMemoryGuardNativeSmokeFastPath(argv)) {
|
|
428
|
+
runMemoryGuardNativeSmokeFastPath();
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
385
431
|
if (isTmuxOwnerIsolationCliArgv(argv)) {
|
|
386
432
|
await runTmuxOwnerIsolationCliFromStdin();
|
|
387
433
|
return;
|
package/src/commands/sdk.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
|
+
import * as os from "node:os";
|
|
3
4
|
import * as path from "node:path";
|
|
4
5
|
import { Args, CliParseError, Command, Flags, renderCommandHelp } from "@gajae-code/utils/cli";
|
|
5
6
|
import type { Args as ParsedArgs } from "../cli/args";
|
|
6
7
|
import { Settings } from "../config/settings";
|
|
7
8
|
import { applyStartupModelProfiles, createSessionManager } from "../main";
|
|
8
9
|
import { initializeExtensions } from "../modes/runtime-init";
|
|
10
|
+
import { ACP_MCP_REQUEST_TIMEOUT_MS, ACP_MCP_STARTUP_HEADROOM_MS } from "../sdk/acp/mcp";
|
|
9
11
|
import { Broker } from "../sdk/broker/broker";
|
|
10
12
|
import { completeBrokerProcess } from "../sdk/broker/internal";
|
|
11
13
|
import {
|
|
@@ -285,17 +287,99 @@ export async function runSessionHost(
|
|
|
285
287
|
throw new Error("SDK startup did not complete before readiness cutoff.");
|
|
286
288
|
}
|
|
287
289
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
created = await createLifecycleAgentSession({ cwd, agentDir, sessionManager: opened.sessionManager });
|
|
293
|
-
} catch (error) {
|
|
290
|
+
// Inlined rather than extracted to a helper: TypeScript's definite-assignment
|
|
291
|
+
// analysis does not see a `Promise<never>` helper as terminating, so hoisting
|
|
292
|
+
// this would make `opened`/`created` "used before assigned" below.
|
|
293
|
+
const registrationFailure = async (error: unknown): Promise<SdkStartupFailure> => {
|
|
294
294
|
const rollback = new SdkStartupRollbackTracker();
|
|
295
295
|
rollback.recordAbsent();
|
|
296
296
|
const failure = normalizeSdkStartupFailure("registration", "failed", error);
|
|
297
297
|
await writeFailure(failure, rollback.result);
|
|
298
|
-
|
|
298
|
+
return failure;
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
let opened: { parsed: ParsedArgs; sessionManager: SessionManager | undefined };
|
|
302
|
+
let created: CreateLifecycleAgentSessionResult;
|
|
303
|
+
let mcpConfigDirectory: string | undefined;
|
|
304
|
+
try {
|
|
305
|
+
let mcpConfigPath: string | undefined;
|
|
306
|
+
try {
|
|
307
|
+
opened = await openLifecycleSessionManager(request, cwd, agentDir);
|
|
308
|
+
if (request.mcpServers && request.mcpServers.length > 0) {
|
|
309
|
+
mcpConfigDirectory = await fs.realpath(await fs.mkdtemp(path.join(os.tmpdir(), "gjc-acp-mcp-")));
|
|
310
|
+
mcpConfigPath = path.join(mcpConfigDirectory, "mcp.json");
|
|
311
|
+
await Bun.write(
|
|
312
|
+
mcpConfigPath,
|
|
313
|
+
JSON.stringify({
|
|
314
|
+
mcpServers: Object.fromEntries(
|
|
315
|
+
request.mcpServers.map(server => [
|
|
316
|
+
server.name,
|
|
317
|
+
"url" in server
|
|
318
|
+
? {
|
|
319
|
+
type: server.type,
|
|
320
|
+
url: server.url,
|
|
321
|
+
...(server.headers ? { headers: server.headers } : {}),
|
|
322
|
+
timeout: ACP_MCP_REQUEST_TIMEOUT_MS,
|
|
323
|
+
}
|
|
324
|
+
: {
|
|
325
|
+
type: "stdio",
|
|
326
|
+
command: server.command,
|
|
327
|
+
args: server.args,
|
|
328
|
+
...(server.env ? { env: server.env } : {}),
|
|
329
|
+
noInheritEnv: true,
|
|
330
|
+
timeout: ACP_MCP_REQUEST_TIMEOUT_MS,
|
|
331
|
+
},
|
|
332
|
+
]),
|
|
333
|
+
),
|
|
334
|
+
}),
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
} catch (error) {
|
|
338
|
+
throw await registrationFailure(error);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// The longer MCP startup ceiling is scoped to ACP lifecycle launches only:
|
|
342
|
+
// it applies when this request actually carried `mcpServers`. Ordinary
|
|
343
|
+
// CLI/SDK `mcpConfigPath` consumers keep the manager's short default.
|
|
344
|
+
//
|
|
345
|
+
// This recheck deliberately sits OUTSIDE the registration catch above.
|
|
346
|
+
// Inside it, the throw would be caught, reclassified as
|
|
347
|
+
// `registration`/`failed`, and written a second time, losing the
|
|
348
|
+
// `startup`/`pending` outcome the readiness cutoff is supposed to report.
|
|
349
|
+
// Session-manager open and MCP config write already consumed part of the
|
|
350
|
+
// budget, so re-read the clock here rather than reusing the earlier check.
|
|
351
|
+
let mcpStartupTimeoutMs: number | undefined;
|
|
352
|
+
if (mcpConfigPath !== undefined) {
|
|
353
|
+
const remaining = request.semanticReadyDeadlineAt - now() - ACP_MCP_STARTUP_HEADROOM_MS;
|
|
354
|
+
if (remaining <= 0) {
|
|
355
|
+
const absent = new SdkStartupRollbackTracker();
|
|
356
|
+
absent.recordAbsent();
|
|
357
|
+
await writeFailure(
|
|
358
|
+
{
|
|
359
|
+
phase: "startup",
|
|
360
|
+
reason: "pending",
|
|
361
|
+
message: "SDK startup did not complete before readiness cutoff.",
|
|
362
|
+
},
|
|
363
|
+
absent.result,
|
|
364
|
+
);
|
|
365
|
+
throw new Error("SDK startup did not complete before readiness cutoff.");
|
|
366
|
+
}
|
|
367
|
+
mcpStartupTimeoutMs = remaining;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
try {
|
|
371
|
+
created = await createLifecycleAgentSession({
|
|
372
|
+
cwd,
|
|
373
|
+
agentDir,
|
|
374
|
+
sessionManager: opened.sessionManager,
|
|
375
|
+
...(mcpConfigPath ? { mcpConfigPath } : {}),
|
|
376
|
+
...(mcpStartupTimeoutMs !== undefined ? { mcpStartupTimeoutMs } : {}),
|
|
377
|
+
});
|
|
378
|
+
} catch (error) {
|
|
379
|
+
throw await registrationFailure(error);
|
|
380
|
+
}
|
|
381
|
+
} finally {
|
|
382
|
+
if (mcpConfigDirectory) await fs.rm(mcpConfigDirectory, { recursive: true, force: true });
|
|
299
383
|
}
|
|
300
384
|
const { parsed } = opened;
|
|
301
385
|
if ("failure" in created) {
|
|
@@ -222,6 +222,7 @@ export function materializeActiveModelProfileAssignments(options: MaterializeMod
|
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
export class ModelProfileCredentialError extends Error {
|
|
225
|
+
readonly code = "authentication_failed";
|
|
225
226
|
readonly profileLabel: string;
|
|
226
227
|
readonly providers: readonly string[];
|
|
227
228
|
|
|
@@ -10,6 +10,7 @@ export interface ModelProfileCatalogItem {
|
|
|
10
10
|
id: string;
|
|
11
11
|
displayName: string;
|
|
12
12
|
source: "builtin" | "configured";
|
|
13
|
+
available?: boolean;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export interface UnknownModelProfileDetails {
|
|
@@ -124,6 +125,18 @@ export function projectModelProfileCatalog(
|
|
|
124
125
|
.sort((left, right) => left.id.localeCompare(right.id));
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
export function isModelProfileProviderAvailable(
|
|
129
|
+
profile: ModelProfileDefinition,
|
|
130
|
+
authenticatedProviders: ReadonlySet<string>,
|
|
131
|
+
): boolean {
|
|
132
|
+
const alternativeGroups = profile.alternativeProviderGroups ?? [];
|
|
133
|
+
const alternativeProviders = new Set(alternativeGroups.flat());
|
|
134
|
+
for (const provider of profile.requiredProviders) {
|
|
135
|
+
if (!alternativeProviders.has(provider) && !authenticatedProviders.has(provider)) return false;
|
|
136
|
+
}
|
|
137
|
+
return alternativeGroups.every(group => group.some(provider => authenticatedProviders.has(provider)));
|
|
138
|
+
}
|
|
139
|
+
|
|
127
140
|
export function isModelProfileError(value: unknown): value is {
|
|
128
141
|
code: ModelProfileErrorCode;
|
|
129
142
|
message: string;
|
|
@@ -207,6 +207,27 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
|
207
207
|
critic: "xai/grok-4.3:xhigh",
|
|
208
208
|
architect: "xai/grok-4.3:xhigh",
|
|
209
209
|
}),
|
|
210
|
+
profile("grok-45-eco", ["xai"], {
|
|
211
|
+
default: "xai/grok-4.5:low",
|
|
212
|
+
executor: "xai/grok-4.5:minimal",
|
|
213
|
+
planner: "xai/grok-4.5:low",
|
|
214
|
+
critic: "xai/grok-4.5:medium",
|
|
215
|
+
architect: "xai/grok-4.5:high",
|
|
216
|
+
}),
|
|
217
|
+
profile("grok-45-medium", ["xai"], {
|
|
218
|
+
default: "xai/grok-4.5:medium",
|
|
219
|
+
executor: "xai/grok-4.5:low",
|
|
220
|
+
planner: "xai/grok-4.5:medium",
|
|
221
|
+
critic: "xai/grok-4.5:high",
|
|
222
|
+
architect: "xai/grok-4.5:high",
|
|
223
|
+
}),
|
|
224
|
+
profile("grok-45-pro", ["xai"], {
|
|
225
|
+
default: "xai/grok-4.5:high",
|
|
226
|
+
executor: "xai/grok-4.5:medium",
|
|
227
|
+
planner: "xai/grok-4.5:high",
|
|
228
|
+
critic: "xai/grok-4.5:high",
|
|
229
|
+
architect: "xai/grok-4.5:high",
|
|
230
|
+
}),
|
|
210
231
|
profile("grok-build-pro", ["grok-build"], {
|
|
211
232
|
default: "grok-build/grok-composer-2.5-fast",
|
|
212
233
|
executor: "grok-build/grok-build",
|
|
@@ -321,6 +342,9 @@ const PROFILE_PRESENTATION: Record<string, ModelProfilePresentation> = {
|
|
|
321
342
|
"grok-eco": { displayName: "Grok Eco", providerGroup: "GROK" },
|
|
322
343
|
"grok-medium": { displayName: "Grok Medium", providerGroup: "GROK" },
|
|
323
344
|
"grok-pro": { displayName: "Grok Pro", providerGroup: "GROK" },
|
|
345
|
+
"grok-45-eco": { displayName: "Grok 4.5 Eco", providerGroup: "GROK" },
|
|
346
|
+
"grok-45-medium": { displayName: "Grok 4.5 Medium", providerGroup: "GROK" },
|
|
347
|
+
"grok-45-pro": { displayName: "Grok 4.5 Pro", providerGroup: "GROK" },
|
|
324
348
|
"grok-build-pro": { displayName: "Grok Build Pro", providerGroup: "GROK" },
|
|
325
349
|
"cursor-eco": { displayName: "Cursor Eco", providerGroup: "CURSOR" },
|
|
326
350
|
"cursor-medium": { displayName: "Cursor Medium", providerGroup: "CURSOR" },
|
|
@@ -309,7 +309,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
309
309
|
},
|
|
310
310
|
"notifications.telegram.toolActivity.enabled": {
|
|
311
311
|
type: "boolean",
|
|
312
|
-
default:
|
|
312
|
+
default: false,
|
|
313
313
|
ui: {
|
|
314
314
|
tab: "notifications",
|
|
315
315
|
label: "Telegram Tool Activity",
|
|
@@ -501,6 +501,11 @@ export const SETTINGS_SCHEMA = {
|
|
|
501
501
|
default: 0.05,
|
|
502
502
|
validate: (value: number) => Number.isFinite(value) && value > 0 && value <= 1,
|
|
503
503
|
},
|
|
504
|
+
"gjc.ralplan.maxIterations": {
|
|
505
|
+
type: "number",
|
|
506
|
+
default: 5,
|
|
507
|
+
validate: (value: number) => Number.isInteger(value) && value >= 1 && value <= 20,
|
|
508
|
+
},
|
|
504
509
|
|
|
505
510
|
// ────────────────────────────────────────────────────────────────────────
|
|
506
511
|
// Appearance
|
|
@@ -1244,7 +1249,8 @@ export const SETTINGS_SCHEMA = {
|
|
|
1244
1249
|
ui: {
|
|
1245
1250
|
tab: "interaction",
|
|
1246
1251
|
label: "Mouse Support",
|
|
1247
|
-
description:
|
|
1252
|
+
description:
|
|
1253
|
+
"Enable GJC session scrolling, drag-to-copy text selection, and overlay row selection with the mouse. Disabled by default to preserve native terminal or tmux scrollback and selection.",
|
|
1248
1254
|
},
|
|
1249
1255
|
},
|
|
1250
1256
|
// Conversation flow
|
|
@@ -2653,6 +2659,47 @@ export const SETTINGS_SCHEMA = {
|
|
|
2653
2659
|
description: "How often the resource GC sweeps browser tabs and stale screenshot directories.",
|
|
2654
2660
|
},
|
|
2655
2661
|
},
|
|
2662
|
+
"memoryGuard.enabled": {
|
|
2663
|
+
type: "boolean",
|
|
2664
|
+
default: false,
|
|
2665
|
+
},
|
|
2666
|
+
"memoryGuard.checkIntervalMs": {
|
|
2667
|
+
type: "number",
|
|
2668
|
+
default: 30_000,
|
|
2669
|
+
validate: (value: number) => Number.isFinite(value) && value > 0,
|
|
2670
|
+
},
|
|
2671
|
+
"memoryGuard.gcThresholdPercent": {
|
|
2672
|
+
type: "number",
|
|
2673
|
+
default: 70,
|
|
2674
|
+
validate: (value: number) => Number.isFinite(value) && value >= 0 && value <= 100,
|
|
2675
|
+
},
|
|
2676
|
+
"memoryGuard.restartThresholdPercent": {
|
|
2677
|
+
type: "number",
|
|
2678
|
+
default: 85,
|
|
2679
|
+
validate: (value: number) => Number.isFinite(value) && value >= 0 && value <= 100,
|
|
2680
|
+
},
|
|
2681
|
+
"memoryGuard.restartThresholdWindowMs": {
|
|
2682
|
+
type: "number",
|
|
2683
|
+
default: 90_000,
|
|
2684
|
+
validate: (value: number) => Number.isFinite(value) && value > 0,
|
|
2685
|
+
},
|
|
2686
|
+
"memoryGuard.cooldownMs": {
|
|
2687
|
+
type: "number",
|
|
2688
|
+
default: 600_000,
|
|
2689
|
+
validate: (value: number) => Number.isFinite(value) && value >= 0,
|
|
2690
|
+
},
|
|
2691
|
+
"memoryGuard.parentReserveMb": {
|
|
2692
|
+
type: "number",
|
|
2693
|
+
default: 1024,
|
|
2694
|
+
validate: (value: number) =>
|
|
2695
|
+
Number.isFinite(value) && value >= 0 && value <= Number.MAX_SAFE_INTEGER / (1024 * 1024),
|
|
2696
|
+
},
|
|
2697
|
+
"memoryGuard.policyLimitMb": {
|
|
2698
|
+
type: "number",
|
|
2699
|
+
default: 0,
|
|
2700
|
+
validate: (value: number) =>
|
|
2701
|
+
Number.isFinite(value) && value >= 0 && value <= Number.MAX_SAFE_INTEGER / (1024 * 1024),
|
|
2702
|
+
},
|
|
2656
2703
|
|
|
2657
2704
|
"computer.enabled": {
|
|
2658
2705
|
type: "boolean",
|
|
@@ -3844,6 +3891,17 @@ export interface ShellMinimizerSettings {
|
|
|
3844
3891
|
maxCaptureBytes: number;
|
|
3845
3892
|
}
|
|
3846
3893
|
|
|
3894
|
+
export interface MemoryGuardSettings {
|
|
3895
|
+
enabled: boolean;
|
|
3896
|
+
checkIntervalMs: number;
|
|
3897
|
+
gcThresholdPercent: number;
|
|
3898
|
+
restartThresholdPercent: number;
|
|
3899
|
+
restartThresholdWindowMs: number;
|
|
3900
|
+
cooldownMs: number;
|
|
3901
|
+
parentReserveMb: number;
|
|
3902
|
+
policyLimitMb: number;
|
|
3903
|
+
}
|
|
3904
|
+
|
|
3847
3905
|
export interface NotificationsSettings {
|
|
3848
3906
|
enabled: boolean;
|
|
3849
3907
|
telegram: {
|
|
@@ -3902,6 +3960,7 @@ export interface GroupTypeMap {
|
|
|
3902
3960
|
statusLine: StatusLineSettings;
|
|
3903
3961
|
thinkingBudgets: ThinkingBudgetsSettings;
|
|
3904
3962
|
stt: SttSettings;
|
|
3963
|
+
memoryGuard: MemoryGuardSettings;
|
|
3905
3964
|
modelRoles: Record<string, ModelSelectorValue>;
|
|
3906
3965
|
modelTags: ModelTagsSettings;
|
|
3907
3966
|
cycleOrder: string[];
|
package/src/config/settings.ts
CHANGED
|
@@ -363,6 +363,7 @@ export class Settings implements NotificationSettingsReader {
|
|
|
363
363
|
/** Pending debounced ordinary save; its queue slot is reserved immediately. */
|
|
364
364
|
#saveTimer?: NodeJS.Timeout;
|
|
365
365
|
#savePromise?: Promise<void>;
|
|
366
|
+
#changeListeners = new Set<(path: SettingPath) => void>();
|
|
366
367
|
#pendingSaveSlot?: PendingSaveSlot;
|
|
367
368
|
|
|
368
369
|
/** Legacy fallback migration warnings emitted once per settings instance. */
|
|
@@ -522,6 +523,11 @@ export class Settings implements NotificationSettingsReader {
|
|
|
522
523
|
return structuredClone(this.#schemaReport);
|
|
523
524
|
}
|
|
524
525
|
|
|
526
|
+
onChanged(listener: (path: SettingPath) => void): () => void {
|
|
527
|
+
this.#changeListeners.add(listener);
|
|
528
|
+
return () => this.#changeListeners.delete(listener);
|
|
529
|
+
}
|
|
530
|
+
|
|
525
531
|
/**
|
|
526
532
|
* Set a setting value (sync).
|
|
527
533
|
* Updates global settings and reserves its background persistence slot before
|
|
@@ -555,6 +561,7 @@ export class Settings implements NotificationSettingsReader {
|
|
|
555
561
|
|
|
556
562
|
const hook = SETTING_HOOKS[path];
|
|
557
563
|
if (hook) hook(value, prev);
|
|
564
|
+
for (const listener of this.#changeListeners) listener(path);
|
|
558
565
|
}
|
|
559
566
|
|
|
560
567
|
/**
|
|
@@ -579,6 +586,7 @@ export class Settings implements NotificationSettingsReader {
|
|
|
579
586
|
|
|
580
587
|
const hook = SETTING_HOOKS[path];
|
|
581
588
|
if (hook) hook(this.get(path), prev);
|
|
589
|
+
for (const listener of this.#changeListeners) listener(path);
|
|
582
590
|
}
|
|
583
591
|
|
|
584
592
|
/**
|
|
@@ -67,14 +67,15 @@ The consensus workflow:
|
|
|
67
67
|
- **Plan-only Critic lane**: independently check quality, principle-option consistency, alternatives, risks, acceptance criteria, and verification; when the plan is thin, request concrete expansion rather than only defects. Persist with `gjc ralplan --write --stage critic --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json`, then return receipt/path plus `OKAY`/`ITERATE`/`REJECT`.
|
|
68
68
|
- **Sequential fallback**: if Critic must evaluate Architect findings, verdict, antithesis, tradeoffs, synthesis, status, or any Architect-produced artifact, await the Architect result before issuing that Architect-dependent Critic pass.
|
|
69
69
|
4. **Review join gate**: before consensus, revision, reconciliation, finalization, or approval, verify both Architect and Critic receipts/verdicts exist for the same Planner artifact/pass (`path`, `sha256`, `stage_n`). A non-`CLEAR` Architect verdict, non-`APPROVE` Architect decision, or any non-`OKAY` Critic verdict routes back to Planner revision; do not finalize from only one review lane.
|
|
70
|
-
5. **Re-review loop** (max 5 iterations): Any non-`OKAY` Critic verdict (`ITERATE` or `REJECT`) or Architect result that is not `CLEAR`/`APPROVE` MUST run the same full closed loop:
|
|
70
|
+
5. **Re-review loop** (max 5 iterations; **runtime-enforced**): Any non-`OKAY` Critic verdict (`ITERATE` or `REJECT`) or Architect result that is not `CLEAR`/`APPROVE` MUST run the same full closed loop:
|
|
71
71
|
a. Collect Architect + Critic feedback
|
|
72
72
|
b. Revise the plan by resuming the SAME persisted Planner subagent with consolidated Architect + Critic feedback (see **Persisted Planner** below); fall back to a fresh Planner spawn only per the fallback routing table
|
|
73
73
|
c. Return to the review fan-out or sequential fallback path above
|
|
74
74
|
- Persist each Planner revision with `gjc ralplan --write --stage revision --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json` before re-review, then pass the receipt/path forward instead of duplicating the full revision markdown in the parent conversation.
|
|
75
75
|
d. Re-join Architect and Critic verdicts for the same revised Planner artifact/pass
|
|
76
76
|
e. Repeat this loop until Critic returns `OKAY` **and** Architect is `CLEAR`/`APPROVE` for the same Planner artifact/pass, or 5 iterations are reached
|
|
77
|
-
f. If 5 iterations are reached without Critic `OKAY` plus Architect `CLEAR`/`APPROVE`,
|
|
77
|
+
f. If 5 iterations are reached without Critic `OKAY` plus Architect `CLEAR`/`APPROVE`, **stop opening further planner/revision passes**. Present the best version to the user (interactive) or surface `PLANNING-STUCK` (headless). Do **not** auto-start implementation.
|
|
78
|
+
g. **Runtime budget (#3165):** native `gjc ralplan --write` refuses a new `planner`/`revision` that would open consensus iteration **> max** (default **5**, overridable via `gjc.ralplan.maxIterations` in project/user `.gjc/settings.json`, integer 1..20). Cap uses the same iteration definition as the HUD (`planner`/`revision` openers in `index.jsonl`). Overflow exits **3**, prints operator-visible **`PLANNING-STUCK`** on stdout (and stderr detail; JSON includes `planning_stuck: true`), and still allows `architect`/`critic` within an already-opened pass plus `post-interview`/`adr`/`final` so the best plan can be escalated to `pending approval` without auto-execution. A new `--run-id` starts a fresh budget.
|
|
78
79
|
6. **Post-ralplan interview** (intent reconciliation gate): After the review join gate has both Critic `OKAY` and Architect `CLEAR`/`APPROVE` for the same Planner artifact/pass, and before the plan is finalized, reconcile the consensus plan against the user's actual intent. The goal is to make sure ralplan did not silently bake in assumptions that conflict with what the user wants.
|
|
79
80
|
a. **Collect open items** from the run: every assumption the Planner/Architect/Critic resolved by assumption rather than by stated fact, every ambiguity flagged during review, and every decision the loop made without explicit user input. Source these from the persisted `planner`/`architect`/`critic`/`revision` stage artifacts, not from memory.
|
|
80
81
|
b. **Cross-check prior context for conflicts**: glob `.gjc/_session-{sessionid}/specs/deep-interview-*.md` and other prior specs/plans/context relevant by topic. For each, list points where the consensus plan contradicts, weakens, or expands beyond a previously crystallized decision, constraint, or non-goal. Cite the conflicting artifact and line/section.
|
|
@@ -103,6 +104,24 @@ The consensus workflow:
|
|
|
103
104
|
|
|
104
105
|
> **Important:** Architect and Critic MAY run in the same parallel batch only for the plan-only Critic lane after Planner persistence. Any Architect-dependent Critic pass MUST remain sequential: await Architect before issuing Critic, then apply the same review join gate before consensus.
|
|
105
106
|
|
|
107
|
+
## Consensus iteration cap (operator contract)
|
|
108
|
+
|
|
109
|
+
- Default max consensus iterations: **5** (`gjc.ralplan.maxIterations`).
|
|
110
|
+
- On cap: exit code **3**, marker **`PLANNING-STUCK`** (stdout), no silent re-loop, no automatic ultragoal/team handoff. Opener budget is `max(index.jsonl openers, on-disk stage-*-{planner,revision}.md count)` so a missing/empty/malformed ledger cannot fail open after prior openers.
|
|
111
|
+
- Headless/CI: treat `PLANNING-STUCK` / exit 3 as terminal planning failure for orchestration/watchdogs.
|
|
112
|
+
- Interactive: present best existing plan via the final approval gate; residual critic findings stay as caveats.
|
|
113
|
+
- Override example (project `.gjc/settings.json`):
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"gjc": {
|
|
118
|
+
"ralplan": {
|
|
119
|
+
"maxIterations": 3
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
106
125
|
|
|
107
126
|
Follow this ralplan-internal consensus workflow for consensus mode details.
|
|
108
127
|
|
|
@@ -279,7 +279,7 @@ An ultragoal story cannot be checkpointed `complete` until the active agent has
|
|
|
279
279
|
- architecture-side: system boundaries, layering, data/control flow, operational risks.
|
|
280
280
|
- product-side: user-visible behavior, acceptance criteria, edge cases, regressions.
|
|
281
281
|
- code-side: maintainability, tests, integration points, and unsafe shortcuts.
|
|
282
|
-
5. Delegate an `executor` QA/red-team lane to build and run the e2e/
|
|
282
|
+
5. Delegate an `executor` QA/red-team lane with typed `executionMode: "ultragoal-red-team"` (preferred) — or assignment text that explicitly labels Ultragoal completion QA/red-team — to build and run the e2e/red-teaming QA suite appropriate for the story. A bare `executorQa` field-name mention is not enough to activate the mode. This lane must try to break the change, not just confirm the happy path. It must start from the approved plan/spec/acceptance criteria, then user-facing contracts, and only then implementation code as supporting evidence. Plan/code mismatches are blockers, not items to paper over with implementation intent.
|
|
283
283
|
6. The executor QA/red-team lane must prove evidence by the real surface under test:
|
|
284
284
|
- GUI/web surfaces require a valid automation transcript plus a non-uniform screenshot. Bare `inlineEvidence` text or typed receipts never prove live GUI/web execution.
|
|
285
285
|
- CLI surfaces require runtime argv replay: `schemaVersion: 1`, `kind: "cli-replay"`, `replaySafe: true`, an allowlisted argv `command`, and replayed output validation. The complete field-by-field replay schema, command allowlist, and `replayExempt` audit contract are specified once in the "For CLI replay artifacts" paragraph below the quality-gate JSON; follow it exactly.
|