@gajae-code/coding-agent 0.2.1 → 0.2.3
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 +59 -1
- package/dist/types/cli/setup-cli.d.ts +1 -0
- package/dist/types/commands/contribution-prep.d.ts +18 -0
- package/dist/types/commands/deep-interview.d.ts +41 -0
- package/dist/types/commands/session.d.ts +24 -0
- package/dist/types/commands/setup.d.ts +3 -0
- package/dist/types/config/model-registry.d.ts +2 -2
- package/dist/types/config/models-config-schema.d.ts +17 -9
- package/dist/types/config/settings-schema.d.ts +37 -24
- package/dist/types/discovery/helpers.d.ts +2 -0
- package/dist/types/extensibility/extensions/types.d.ts +6 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +33 -0
- package/dist/types/gjc-runtime/goal-mode-request.d.ts +1 -1
- package/dist/types/gjc-runtime/launch-tmux.d.ts +12 -11
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +25 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +13 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +37 -5
- package/dist/types/gjc-runtime/tmux-common.d.ts +41 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +17 -0
- package/dist/types/goals/runtime.d.ts +3 -9
- package/dist/types/goals/state.d.ts +3 -6
- package/dist/types/goals/tools/goal-tool.d.ts +1 -69
- package/dist/types/hooks/skill-state.d.ts +5 -0
- package/dist/types/memories/index.d.ts +1 -1
- package/dist/types/memory-backend/local-backend.d.ts +3 -3
- package/dist/types/modes/components/hook-selector.d.ts +7 -0
- package/dist/types/modes/components/settings-selector.d.ts +0 -2
- package/dist/types/modes/components/status-line/types.d.ts +0 -3
- package/dist/types/modes/components/status-line.d.ts +0 -3
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -12
- package/dist/types/modes/theme/defaults/index.d.ts +0 -2
- package/dist/types/modes/theme/theme.d.ts +1 -2
- package/dist/types/modes/types.d.ts +1 -7
- package/dist/types/modes/utils/context-usage.d.ts +6 -2
- package/dist/types/sdk.d.ts +6 -2
- package/dist/types/session/agent-session.d.ts +47 -1
- package/dist/types/session/contribution-prep.d.ts +47 -0
- package/dist/types/session/session-manager.d.ts +3 -0
- package/dist/types/setup/model-onboarding-guidance.d.ts +1 -0
- package/dist/types/setup/provider-onboarding.d.ts +29 -5
- package/dist/types/skill-state/active-state.d.ts +30 -1
- package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +6 -1
- package/dist/types/skill-state/initial-phase.d.ts +12 -0
- package/dist/types/skill-state/workflow-hud.d.ts +9 -4
- package/dist/types/skill-state/workflow-state-contract.d.ts +34 -0
- package/dist/types/task/executor.d.ts +2 -0
- package/dist/types/task/types.d.ts +11 -0
- package/dist/types/tools/index.d.ts +20 -1
- package/dist/types/tools/skill.d.ts +47 -0
- package/dist/types/utils/changelog.d.ts +18 -2
- package/package.json +7 -7
- package/src/cli/args.ts +3 -2
- package/src/cli/setup-cli.ts +26 -12
- package/src/cli.ts +7 -1
- package/src/commands/contribution-prep.ts +41 -0
- package/src/commands/deep-interview.ts +30 -23
- package/src/commands/launch.ts +10 -1
- package/src/commands/ralplan.ts +10 -22
- package/src/commands/session.ts +150 -0
- package/src/commands/setup.ts +2 -0
- package/src/commands/state.ts +15 -4
- package/src/commands/team.ts +23 -3
- package/src/config/model-registry.ts +10 -2
- package/src/config/models-config-schema.ts +120 -102
- package/src/config/settings-schema.ts +42 -25
- package/src/config.ts +1 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +32 -13
- package/src/defaults/gjc/skills/ralplan/SKILL.md +22 -2
- package/src/defaults/gjc/skills/team/SKILL.md +39 -7
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +33 -25
- package/src/discovery/helpers.ts +24 -1
- package/src/eval/py/prelude.py +1 -1
- package/src/extensibility/extensions/types.ts +6 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +546 -0
- package/src/gjc-runtime/goal-mode-request.ts +2 -19
- package/src/gjc-runtime/launch-tmux.ts +83 -43
- package/src/gjc-runtime/ralplan-runtime.ts +460 -0
- package/src/gjc-runtime/state-runtime.ts +731 -0
- package/src/gjc-runtime/team-runtime.ts +708 -52
- package/src/gjc-runtime/tmux-common.ts +119 -0
- package/src/gjc-runtime/tmux-sessions.ts +165 -0
- package/src/gjc-runtime/ultragoal-guard.ts +6 -3
- package/src/gjc-runtime/ultragoal-runtime.ts +5 -4
- package/src/goals/runtime.ts +38 -144
- package/src/goals/state.ts +36 -7
- package/src/goals/tools/goal-tool.ts +15 -172
- package/src/hooks/skill-state.ts +39 -18
- package/src/internal-urls/docs-index.generated.ts +5 -4
- package/src/internal-urls/memory-protocol.ts +3 -2
- package/src/main.ts +2 -3
- package/src/memories/index.ts +2 -1
- package/src/memory-backend/local-backend.ts +14 -6
- package/src/modes/components/hook-selector.ts +156 -1
- package/src/modes/components/settings-selector.ts +5 -12
- package/src/modes/components/skill-hud/render.ts +4 -0
- package/src/modes/components/status-line/segments.ts +5 -16
- package/src/modes/components/status-line/types.ts +0 -3
- package/src/modes/components/status-line.ts +0 -6
- package/src/modes/controllers/command-controller.ts +27 -4
- package/src/modes/controllers/extension-ui-controller.ts +1 -0
- package/src/modes/controllers/input-controller.ts +0 -15
- package/src/modes/controllers/selector-controller.ts +4 -11
- package/src/modes/interactive-mode.ts +18 -219
- package/src/modes/theme/defaults/dark-poimandres.json +0 -1
- package/src/modes/theme/defaults/light-poimandres.json +0 -1
- package/src/modes/theme/theme.ts +0 -6
- package/src/modes/types.ts +1 -7
- package/src/modes/utils/context-usage.ts +66 -17
- package/src/prompts/agents/architect.md +3 -0
- package/src/prompts/agents/executor.md +2 -0
- package/src/prompts/agents/frontmatter.md +1 -0
- package/src/prompts/goals/goal-continuation.md +1 -4
- package/src/prompts/goals/goal-mode-active.md +3 -5
- package/src/prompts/system/subagent-system-prompt.md +6 -0
- package/src/prompts/system/system-prompt.md +5 -7
- package/src/prompts/tools/goal.md +4 -4
- package/src/prompts/tools/skill.md +28 -0
- package/src/prompts/tools/task.md +3 -0
- package/src/sdk.ts +51 -11
- package/src/session/agent-session.ts +222 -21
- package/src/session/contribution-prep.ts +320 -0
- package/src/session/session-manager.ts +9 -1
- package/src/setup/model-onboarding-guidance.ts +6 -3
- package/src/setup/provider-onboarding.ts +177 -16
- package/src/skill-state/active-state.ts +188 -25
- package/src/skill-state/deep-interview-mutation-guard.ts +72 -21
- package/src/skill-state/initial-phase.ts +17 -0
- package/src/skill-state/workflow-hud.ts +23 -5
- package/src/skill-state/workflow-state-contract.ts +121 -0
- package/src/slash-commands/builtin-registry.ts +75 -25
- package/src/slash-commands/helpers/context-report.ts +123 -13
- package/src/task/agents.ts +1 -0
- package/src/task/commands.ts +1 -5
- package/src/task/executor.ts +9 -1
- package/src/task/index.ts +91 -4
- package/src/task/types.ts +6 -0
- package/src/tools/ask.ts +2 -0
- package/src/tools/gh.ts +212 -2
- package/src/tools/index.ts +25 -6
- package/src/tools/skill.ts +153 -0
- package/src/utils/changelog.ts +67 -44
- package/dist/types/commands/gjc-runtime-bridge.d.ts +0 -30
- package/dist/types/commands/question.d.ts +0 -7
- package/dist/types/modes/loop-limit.d.ts +0 -22
- package/src/commands/gjc-runtime-bridge.ts +0 -227
- package/src/commands/question.ts +0 -12
- package/src/modes/loop-limit.ts +0 -140
- package/src/prompts/commands/orchestrate.md +0 -49
- package/src/prompts/goals/goal-budget-limit.md +0 -16
- package/src/prompts/tools/create-goal.md +0 -3
- package/src/prompts/tools/get-goal.md +0 -3
- package/src/prompts/tools/update-goal.md +0 -3
|
@@ -1,12 +1,30 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import type { Args } from "../cli/args";
|
|
3
|
+
import {
|
|
4
|
+
buildGjcTmuxProfileCommands,
|
|
5
|
+
buildGjcTmuxSessionName,
|
|
6
|
+
buildGjcTmuxSessionSlug,
|
|
7
|
+
GJC_DEFAULT_TMUX_SESSION,
|
|
8
|
+
GJC_TMUX_COMMAND_ENV,
|
|
9
|
+
GJC_TMUX_MOUSE_ENV,
|
|
10
|
+
GJC_TMUX_PROFILE_ENV,
|
|
11
|
+
GJC_TMUX_SESSION_PREFIX,
|
|
12
|
+
type GjcTmuxProfileCommand,
|
|
13
|
+
resolveGjcTmuxCommand,
|
|
14
|
+
} from "./tmux-common";
|
|
15
|
+
import { findGjcTmuxSessionByBranch } from "./tmux-sessions";
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
buildGjcTmuxProfileCommands,
|
|
19
|
+
GJC_DEFAULT_TMUX_SESSION,
|
|
20
|
+
GJC_TMUX_COMMAND_ENV,
|
|
21
|
+
GJC_TMUX_MOUSE_ENV,
|
|
22
|
+
GJC_TMUX_PROFILE_ENV,
|
|
23
|
+
GJC_TMUX_SESSION_PREFIX,
|
|
24
|
+
};
|
|
3
25
|
|
|
4
|
-
export const GJC_DEFAULT_TMUX_SESSION = "gajae_code";
|
|
5
26
|
export const GJC_TMUX_LAUNCHED_ENV = "GJC_TMUX_LAUNCHED";
|
|
6
27
|
export const GJC_LAUNCH_POLICY_ENV = "GJC_LAUNCH_POLICY";
|
|
7
|
-
export const GJC_TMUX_COMMAND_ENV = "GJC_TMUX_COMMAND";
|
|
8
|
-
export const GJC_TMUX_PROFILE_ENV = "GJC_TMUX_PROFILE";
|
|
9
|
-
export const GJC_TMUX_MOUSE_ENV = "GJC_MOUSE";
|
|
10
28
|
|
|
11
29
|
type LaunchPolicy = "direct" | "tmux";
|
|
12
30
|
|
|
@@ -26,6 +44,10 @@ export interface TmuxLaunchContext {
|
|
|
26
44
|
tty?: TtyState;
|
|
27
45
|
spawnSync?: TmuxSpawnSync;
|
|
28
46
|
tmuxAvailable?: boolean;
|
|
47
|
+
worktreeBranch?: string | null;
|
|
48
|
+
currentBranch?: string | null;
|
|
49
|
+
existingBranchSessionName?: string | null;
|
|
50
|
+
project?: string | null;
|
|
29
51
|
}
|
|
30
52
|
|
|
31
53
|
export interface TmuxSpawnResult {
|
|
@@ -50,12 +72,9 @@ export interface TmuxLaunchPlan {
|
|
|
50
72
|
cwd: string;
|
|
51
73
|
innerCommand: string;
|
|
52
74
|
newSessionArgs: string[];
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
export interface GjcTmuxProfileCommand {
|
|
57
|
-
description: string;
|
|
58
|
-
args: string[];
|
|
75
|
+
branch?: string | null;
|
|
76
|
+
attachSessionName?: string;
|
|
77
|
+
project?: string | null;
|
|
59
78
|
}
|
|
60
79
|
|
|
61
80
|
export interface GjcTmuxProfileResult {
|
|
@@ -70,6 +89,9 @@ export interface GjcTmuxProfileContext {
|
|
|
70
89
|
cwd?: string;
|
|
71
90
|
env?: NodeJS.ProcessEnv;
|
|
72
91
|
spawnSync?: TmuxSpawnSync;
|
|
92
|
+
branch?: string | null;
|
|
93
|
+
branchSlug?: string | null;
|
|
94
|
+
project?: string | null;
|
|
73
95
|
}
|
|
74
96
|
|
|
75
97
|
interface CommandResolutionContext {
|
|
@@ -103,35 +125,14 @@ function shellQuote(value: string): string {
|
|
|
103
125
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
104
126
|
}
|
|
105
127
|
|
|
106
|
-
function envDisabled(value: string | undefined): boolean {
|
|
107
|
-
const normalized = value?.trim().toLowerCase();
|
|
108
|
-
return normalized === "0" || normalized === "false" || normalized === "off" || normalized === "no";
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export function buildGjcTmuxProfileCommands(
|
|
112
|
-
target: string,
|
|
113
|
-
env: NodeJS.ProcessEnv = process.env,
|
|
114
|
-
): GjcTmuxProfileCommand[] {
|
|
115
|
-
if (envDisabled(env[GJC_TMUX_PROFILE_ENV])) return [];
|
|
116
|
-
const commands: GjcTmuxProfileCommand[] = [
|
|
117
|
-
{ description: "mark GJC tmux ownership", args: ["set-option", "-t", target, "@gjc-profile", "1"] },
|
|
118
|
-
{ description: "enable tmux clipboard integration", args: ["set-option", "-t", target, "set-clipboard", "on"] },
|
|
119
|
-
{
|
|
120
|
-
description: "make copy-mode selection readable",
|
|
121
|
-
args: ["set-window-option", "-t", target, "mode-style", "fg=colour231,bg=colour60"],
|
|
122
|
-
},
|
|
123
|
-
];
|
|
124
|
-
if (!envDisabled(env[GJC_TMUX_MOUSE_ENV]))
|
|
125
|
-
commands.unshift({
|
|
126
|
-
description: "enable tmux mouse scrolling",
|
|
127
|
-
args: ["set-option", "-t", target, "mouse", "on"],
|
|
128
|
-
});
|
|
129
|
-
return commands;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
128
|
export function applyGjcTmuxProfile(context: GjcTmuxProfileContext): GjcTmuxProfileResult {
|
|
133
129
|
const env = context.env ?? process.env;
|
|
134
|
-
const
|
|
130
|
+
const branchSlug = context.branch ? buildGjcTmuxSessionSlug(context.branch) : (context.branchSlug ?? null);
|
|
131
|
+
const commands = buildGjcTmuxProfileCommands(context.target, env, {
|
|
132
|
+
branch: context.branch ?? null,
|
|
133
|
+
branchSlug,
|
|
134
|
+
project: context.project ?? null,
|
|
135
|
+
});
|
|
135
136
|
if (commands.length === 0) return { skipped: true, commands: [], failures: [] };
|
|
136
137
|
const spawnSync = context.spawnSync ?? defaultSpawnSync;
|
|
137
138
|
const cwd = context.cwd ?? process.cwd();
|
|
@@ -160,6 +161,25 @@ function buildInnerCommand(context: CommandResolutionContext, rawArgs: string[])
|
|
|
160
161
|
return `exec env ${GJC_TMUX_LAUNCHED_ENV}=1 ${quoted}`;
|
|
161
162
|
}
|
|
162
163
|
|
|
164
|
+
function readCurrentBranch(cwd: string): string | null {
|
|
165
|
+
try {
|
|
166
|
+
const result = Bun.spawnSync(["git", "symbolic-ref", "--quiet", "--short", "HEAD"], {
|
|
167
|
+
cwd,
|
|
168
|
+
stdout: "pipe",
|
|
169
|
+
stderr: "ignore",
|
|
170
|
+
});
|
|
171
|
+
if (result.exitCode !== 0) return null;
|
|
172
|
+
const branch = result.stdout.toString().trim();
|
|
173
|
+
return branch || null;
|
|
174
|
+
} catch {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function cleanupCreatedTmuxSession(plan: TmuxLaunchPlan, spawnSync: TmuxSpawnSync, options: TmuxSpawnOptions): void {
|
|
180
|
+
spawnSync(plan.tmuxCommand, ["kill-session", "-t", `=${plan.sessionName}`], options);
|
|
181
|
+
}
|
|
182
|
+
|
|
163
183
|
export function buildDefaultTmuxLaunchPlan(context: TmuxLaunchContext): TmuxLaunchPlan | undefined {
|
|
164
184
|
const env = context.env ?? process.env;
|
|
165
185
|
const policy = parseLaunchPolicy(env);
|
|
@@ -171,10 +191,18 @@ export function buildDefaultTmuxLaunchPlan(context: TmuxLaunchContext): TmuxLaun
|
|
|
171
191
|
if (policy === "tmux" && !isInteractiveRootLaunch(context.parsed, tty)) return undefined;
|
|
172
192
|
|
|
173
193
|
const cwd = context.cwd ?? process.cwd();
|
|
174
|
-
const
|
|
175
|
-
const
|
|
194
|
+
const branch = context.worktreeBranch ?? context.currentBranch ?? readCurrentBranch(cwd);
|
|
195
|
+
const project = context.project ?? cwd;
|
|
196
|
+
const sessionName = buildGjcTmuxSessionName(env, { branch });
|
|
197
|
+
const tmuxCommand = resolveGjcTmuxCommand(env);
|
|
176
198
|
const tmuxAvailable = context.tmuxAvailable ?? Bun.which(tmuxCommand) !== null;
|
|
177
199
|
if (!tmuxAvailable) return undefined;
|
|
200
|
+
const existingBranchSessionName =
|
|
201
|
+
"existingBranchSessionName" in context
|
|
202
|
+
? (context.existingBranchSessionName ?? undefined)
|
|
203
|
+
: context.worktreeBranch
|
|
204
|
+
? findGjcTmuxSessionByBranch(context.worktreeBranch, env, project)?.name
|
|
205
|
+
: undefined;
|
|
178
206
|
const innerCommand = buildInnerCommand(
|
|
179
207
|
{
|
|
180
208
|
cwd,
|
|
@@ -189,7 +217,9 @@ export function buildDefaultTmuxLaunchPlan(context: TmuxLaunchContext): TmuxLaun
|
|
|
189
217
|
cwd,
|
|
190
218
|
innerCommand,
|
|
191
219
|
newSessionArgs: ["new-session", "-d", "-s", sessionName, "-c", cwd, innerCommand],
|
|
192
|
-
|
|
220
|
+
branch,
|
|
221
|
+
project,
|
|
222
|
+
attachSessionName: existingBranchSessionName,
|
|
193
223
|
};
|
|
194
224
|
}
|
|
195
225
|
|
|
@@ -217,17 +247,27 @@ export function launchDefaultTmuxIfNeeded(context: TmuxLaunchContext): boolean {
|
|
|
217
247
|
stdout: "inherit",
|
|
218
248
|
stderr: "inherit",
|
|
219
249
|
};
|
|
250
|
+
if (plan.attachSessionName) {
|
|
251
|
+
const attached = spawnSync(plan.tmuxCommand, ["attach-session", "-t", `=${plan.attachSessionName}`], options);
|
|
252
|
+
return attached.exitCode === 0;
|
|
253
|
+
}
|
|
220
254
|
const created = spawnSync(plan.tmuxCommand, plan.newSessionArgs, options);
|
|
221
255
|
if (created.exitCode === 0) {
|
|
222
|
-
applyGjcTmuxProfile({
|
|
256
|
+
const profile = applyGjcTmuxProfile({
|
|
223
257
|
tmuxCommand: plan.tmuxCommand,
|
|
224
258
|
target: plan.sessionName,
|
|
225
259
|
cwd: plan.cwd,
|
|
226
260
|
env,
|
|
227
261
|
spawnSync,
|
|
262
|
+
branch: plan.branch,
|
|
263
|
+
project: plan.project,
|
|
228
264
|
});
|
|
265
|
+
if (profile.failures.length > 0) {
|
|
266
|
+
cleanupCreatedTmuxSession(plan, spawnSync, options);
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
229
269
|
}
|
|
230
|
-
|
|
231
|
-
|
|
270
|
+
if (created.exitCode !== 0) return false;
|
|
271
|
+
const attached = spawnSync(plan.tmuxCommand, ["attach-session", "-t", plan.sessionName], options);
|
|
232
272
|
return attached.exitCode === 0;
|
|
233
273
|
}
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs/promises";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { syncSkillActiveState } from "../skill-state/active-state";
|
|
5
|
+
import { buildRalplanHudSummary } from "../skill-state/workflow-hud";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Native implementation of `gjc ralplan`.
|
|
9
|
+
*
|
|
10
|
+
* Two invocation shapes are handled natively:
|
|
11
|
+
*
|
|
12
|
+
* 1. **Consensus handoff**: `gjc ralplan [--interactive] [--deliberate] [--architect <kind>]
|
|
13
|
+
* [--critic <kind>] [--session-id <id>] "<task>"` validates the documented flag surface,
|
|
14
|
+
* seeds `.gjc/state/ralplan-state.json`, and updates the shared HUD rail via
|
|
15
|
+
* `syncSkillActiveState`. The CLI never *runs* the Planner / Architect / Critic loop itself —
|
|
16
|
+
* that lives in the bundled `/skill:ralplan` skill — but it accepts every documented flag so
|
|
17
|
+
* scripted users see a useful response and the active run is visible to the TUI.
|
|
18
|
+
*
|
|
19
|
+
* 2. **Artifact write**: `gjc ralplan --write --stage <type> --stage_n <N> --artifact
|
|
20
|
+
* <path-or-string> [--run-id <id>] [--session-id <id>] [--json]` persists Planner / Architect
|
|
21
|
+
* / Critic / revision / ADR / final markdown under `.gjc/plans/ralplan/<run-id>/`, maintains
|
|
22
|
+
* an `index.jsonl` audit log, copies `final` stages to `pending-approval.md`, and advances
|
|
23
|
+
* the HUD chip to reflect the latest persisted stage.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export interface RalplanCommandResult {
|
|
27
|
+
status: number;
|
|
28
|
+
stdout?: string;
|
|
29
|
+
stderr?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const KNOWN_STAGES = ["planner", "architect", "critic", "revision", "adr", "final"] as const;
|
|
33
|
+
type RalplanStage = (typeof KNOWN_STAGES)[number];
|
|
34
|
+
|
|
35
|
+
const KNOWN_ARCHITECT_KINDS = new Set(["openai-code"]);
|
|
36
|
+
const KNOWN_CRITIC_KINDS = new Set(["openai-code"]);
|
|
37
|
+
|
|
38
|
+
const PATH_COMPONENT_RE = /^[A-Za-z0-9_-][A-Za-z0-9._-]{0,63}$/;
|
|
39
|
+
|
|
40
|
+
class RalplanCommandError extends Error {
|
|
41
|
+
constructor(
|
|
42
|
+
public readonly exitStatus: number,
|
|
43
|
+
message: string,
|
|
44
|
+
) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.name = "RalplanCommandError";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const VALUE_FLAGS = new Set([
|
|
51
|
+
"--stage",
|
|
52
|
+
"--stage_n",
|
|
53
|
+
"--artifact",
|
|
54
|
+
"--run-id",
|
|
55
|
+
"--session-id",
|
|
56
|
+
"--architect",
|
|
57
|
+
"--critic",
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
function flagValue(args: readonly string[], flag: string): string | undefined {
|
|
61
|
+
const index = args.indexOf(flag);
|
|
62
|
+
if (index < 0) return undefined;
|
|
63
|
+
return args[index + 1];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function hasFlag(args: readonly string[], flag: string): boolean {
|
|
67
|
+
return args.includes(flag);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function isRalplanArtifactWriteInvocation(args: readonly string[]): boolean {
|
|
71
|
+
return hasFlag(args, "--write");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function assertSafePathComponent(value: string, label: string): void {
|
|
75
|
+
if (!PATH_COMPONENT_RE.test(value) || value.includes("..")) {
|
|
76
|
+
throw new RalplanCommandError(2, `invalid path component for --${label}: ${value}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function assertKnownStage(stage: string): asserts stage is RalplanStage {
|
|
81
|
+
if (!(KNOWN_STAGES as readonly string[]).includes(stage)) {
|
|
82
|
+
throw new RalplanCommandError(2, `unknown --stage: ${stage}. Expected one of: ${KNOWN_STAGES.join(", ")}.`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function parseStageN(raw: string | undefined): number {
|
|
87
|
+
if (!raw) throw new RalplanCommandError(2, "--stage_n is required");
|
|
88
|
+
if (!/^[1-9][0-9]{0,2}$/.test(raw)) {
|
|
89
|
+
throw new RalplanCommandError(2, `invalid --stage_n: ${raw}. Expected integer 1..999.`);
|
|
90
|
+
}
|
|
91
|
+
const value = Number.parseInt(raw, 10);
|
|
92
|
+
if (value < 1 || value > 999) {
|
|
93
|
+
throw new RalplanCommandError(2, `invalid --stage_n: ${raw}. Expected integer 1..999.`);
|
|
94
|
+
}
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function pad2(value: number): string {
|
|
99
|
+
return value.toString().padStart(2, "0");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function defaultRunId(now: Date = new Date()): string {
|
|
103
|
+
const yyyy = now.getUTCFullYear().toString().padStart(4, "0");
|
|
104
|
+
const mm = (now.getUTCMonth() + 1).toString().padStart(2, "0");
|
|
105
|
+
const dd = now.getUTCDate().toString().padStart(2, "0");
|
|
106
|
+
const hh = now.getUTCHours().toString().padStart(2, "0");
|
|
107
|
+
const min = now.getUTCMinutes().toString().padStart(2, "0");
|
|
108
|
+
const suffix = randomBytes(2).toString("hex");
|
|
109
|
+
return `${yyyy}-${mm}-${dd}-${hh}${min}-${suffix}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function resolveArtifactContent(rawArtifact: string, cwd: string): Promise<string> {
|
|
113
|
+
const candidate = path.isAbsolute(rawArtifact) ? rawArtifact : path.resolve(cwd, rawArtifact);
|
|
114
|
+
try {
|
|
115
|
+
const stat = await fs.stat(candidate);
|
|
116
|
+
if (stat.isFile()) return await fs.readFile(candidate, "utf-8");
|
|
117
|
+
} catch (error) {
|
|
118
|
+
const err = error as NodeJS.ErrnoException;
|
|
119
|
+
if (err.code !== "ENOENT" && err.code !== "ENOTDIR") {
|
|
120
|
+
throw new RalplanCommandError(2, `failed to read --artifact ${candidate}: ${err.message}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return rawArtifact;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* ------------------------------ artifact write ------------------------------ */
|
|
127
|
+
|
|
128
|
+
interface ResolvedArtifactArgs {
|
|
129
|
+
stage: RalplanStage;
|
|
130
|
+
stageN: number;
|
|
131
|
+
runId: string;
|
|
132
|
+
artifact: string;
|
|
133
|
+
sessionId: string | undefined;
|
|
134
|
+
json: boolean;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function ralplanStatePath(cwd: string, sessionId: string | undefined): string {
|
|
138
|
+
const stateDir = sessionId
|
|
139
|
+
? path.join(cwd, ".gjc", "state", "sessions", encodeSessionSegment(sessionId))
|
|
140
|
+
: path.join(cwd, ".gjc", "state");
|
|
141
|
+
return path.join(stateDir, "ralplan-state.json");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function readActiveRunId(cwd: string, sessionId: string | undefined): Promise<string | undefined> {
|
|
145
|
+
try {
|
|
146
|
+
const raw = await fs.readFile(ralplanStatePath(cwd, sessionId), "utf-8");
|
|
147
|
+
const parsed = JSON.parse(raw) as { run_id?: unknown };
|
|
148
|
+
const candidate = typeof parsed.run_id === "string" ? parsed.run_id.trim() : "";
|
|
149
|
+
if (!candidate) return undefined;
|
|
150
|
+
assertSafePathComponent(candidate, "run-id");
|
|
151
|
+
return candidate;
|
|
152
|
+
} catch {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function persistActiveRunId(cwd: string, sessionId: string | undefined, runId: string): Promise<void> {
|
|
158
|
+
const statePath = ralplanStatePath(cwd, sessionId);
|
|
159
|
+
let existing: Record<string, unknown> = {};
|
|
160
|
+
try {
|
|
161
|
+
const raw = await fs.readFile(statePath, "utf-8");
|
|
162
|
+
const parsed = JSON.parse(raw);
|
|
163
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
164
|
+
existing = parsed as Record<string, unknown>;
|
|
165
|
+
}
|
|
166
|
+
} catch {
|
|
167
|
+
// fresh receipt; fall through to create
|
|
168
|
+
}
|
|
169
|
+
if (existing.run_id === runId) return;
|
|
170
|
+
existing.run_id = runId;
|
|
171
|
+
if (typeof existing.skill !== "string") existing.skill = "ralplan";
|
|
172
|
+
if (typeof existing.active !== "boolean") existing.active = true;
|
|
173
|
+
existing.updated_at = new Date().toISOString();
|
|
174
|
+
await fs.mkdir(path.dirname(statePath), { recursive: true });
|
|
175
|
+
await fs.writeFile(statePath, `${JSON.stringify(existing, null, 2)}\n`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function resolveArtifactArgs(args: readonly string[], cwd: string): Promise<ResolvedArtifactArgs> {
|
|
179
|
+
const stage = flagValue(args, "--stage");
|
|
180
|
+
if (!stage) throw new RalplanCommandError(2, "--stage is required for ralplan --write");
|
|
181
|
+
assertKnownStage(stage);
|
|
182
|
+
|
|
183
|
+
const stageN = parseStageN(flagValue(args, "--stage_n"));
|
|
184
|
+
|
|
185
|
+
const rawArtifact = flagValue(args, "--artifact");
|
|
186
|
+
if (rawArtifact === undefined || rawArtifact === "") {
|
|
187
|
+
throw new RalplanCommandError(2, "--artifact is required for ralplan --write");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const sessionIdRaw = flagValue(args, "--session-id")?.trim();
|
|
191
|
+
const sessionId = sessionIdRaw || undefined;
|
|
192
|
+
if (sessionId) assertSafePathComponent(sessionId, "session-id");
|
|
193
|
+
|
|
194
|
+
// Precedence for run_id:
|
|
195
|
+
// 1. explicit --run-id flag
|
|
196
|
+
// 2. existing run_id field in .gjc/state[/sessions/<id>]/ralplan-state.json
|
|
197
|
+
// 3. explicit --session-id flag (use as run id)
|
|
198
|
+
// 4. freshly generated default run id
|
|
199
|
+
const explicitRunId = flagValue(args, "--run-id")?.trim();
|
|
200
|
+
const runId = explicitRunId || (await readActiveRunId(cwd, sessionId)) || sessionIdRaw || defaultRunId();
|
|
201
|
+
assertSafePathComponent(runId, "run-id");
|
|
202
|
+
// Persist the active run id so later writes in the same loop land in the same directory.
|
|
203
|
+
await persistActiveRunId(cwd, sessionId, runId);
|
|
204
|
+
|
|
205
|
+
const artifact = await resolveArtifactContent(rawArtifact, cwd);
|
|
206
|
+
return { stage: stage as RalplanStage, stageN, runId, artifact, sessionId, json: hasFlag(args, "--json") };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
interface PersistedArtifact {
|
|
210
|
+
runId: string;
|
|
211
|
+
path: string;
|
|
212
|
+
stage: RalplanStage;
|
|
213
|
+
stageN: number;
|
|
214
|
+
sha256: string;
|
|
215
|
+
createdAt: string;
|
|
216
|
+
pendingApprovalPath?: string;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function persistArtifact(resolved: ResolvedArtifactArgs, cwd: string): Promise<PersistedArtifact> {
|
|
220
|
+
const runDir = path.join(cwd, ".gjc", "plans", "ralplan", resolved.runId);
|
|
221
|
+
await fs.mkdir(runDir, { recursive: true });
|
|
222
|
+
const fileName = `stage-${pad2(resolved.stageN)}-${resolved.stage}.md`;
|
|
223
|
+
const filePath = path.join(runDir, fileName);
|
|
224
|
+
const content = resolved.artifact.endsWith("\n") ? resolved.artifact : `${resolved.artifact}\n`;
|
|
225
|
+
await fs.writeFile(filePath, content);
|
|
226
|
+
|
|
227
|
+
const sha256 = createHash("sha256").update(content).digest("hex");
|
|
228
|
+
const createdAt = new Date().toISOString();
|
|
229
|
+
const indexLine = `${JSON.stringify({
|
|
230
|
+
stage: resolved.stage,
|
|
231
|
+
stage_n: resolved.stageN,
|
|
232
|
+
path: filePath,
|
|
233
|
+
created_at: createdAt,
|
|
234
|
+
sha256,
|
|
235
|
+
})}\n`;
|
|
236
|
+
await fs.appendFile(path.join(runDir, "index.jsonl"), indexLine);
|
|
237
|
+
|
|
238
|
+
let pendingApprovalPath: string | undefined;
|
|
239
|
+
if (resolved.stage === "final") {
|
|
240
|
+
pendingApprovalPath = path.join(runDir, "pending-approval.md");
|
|
241
|
+
await fs.writeFile(pendingApprovalPath, content);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
runId: resolved.runId,
|
|
246
|
+
path: filePath,
|
|
247
|
+
stage: resolved.stage,
|
|
248
|
+
stageN: resolved.stageN,
|
|
249
|
+
sha256,
|
|
250
|
+
createdAt,
|
|
251
|
+
pendingApprovalPath,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function syncRalplanHud(options: {
|
|
256
|
+
cwd: string;
|
|
257
|
+
sessionId?: string;
|
|
258
|
+
stage: string;
|
|
259
|
+
pendingApproval: boolean;
|
|
260
|
+
iteration?: number;
|
|
261
|
+
latestSummary?: string;
|
|
262
|
+
}): Promise<void> {
|
|
263
|
+
try {
|
|
264
|
+
await syncSkillActiveState({
|
|
265
|
+
cwd: options.cwd,
|
|
266
|
+
skill: "ralplan",
|
|
267
|
+
active: !options.pendingApproval || options.stage === "final",
|
|
268
|
+
phase: options.stage,
|
|
269
|
+
sessionId: options.sessionId,
|
|
270
|
+
source: "gjc-ralplan-native",
|
|
271
|
+
hud: buildRalplanHudSummary({
|
|
272
|
+
stage: options.stage,
|
|
273
|
+
iteration: options.iteration,
|
|
274
|
+
pendingApproval: options.pendingApproval,
|
|
275
|
+
latestSummary: options.latestSummary,
|
|
276
|
+
updatedAt: new Date().toISOString(),
|
|
277
|
+
}),
|
|
278
|
+
});
|
|
279
|
+
} catch {
|
|
280
|
+
// HUD sync is best-effort and must not change command semantics.
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async function handleArtifactWrite(args: readonly string[], cwd: string): Promise<RalplanCommandResult> {
|
|
285
|
+
const resolved = await resolveArtifactArgs(args, cwd);
|
|
286
|
+
const persisted = await persistArtifact(resolved, cwd);
|
|
287
|
+
await syncRalplanHud({
|
|
288
|
+
cwd,
|
|
289
|
+
sessionId: resolved.sessionId,
|
|
290
|
+
stage: persisted.stage,
|
|
291
|
+
pendingApproval: persisted.stage === "final",
|
|
292
|
+
iteration: persisted.stageN,
|
|
293
|
+
latestSummary: `persisted ${persisted.stage} stage ${persisted.stageN}`,
|
|
294
|
+
});
|
|
295
|
+
const payload: Record<string, unknown> = {
|
|
296
|
+
run_id: persisted.runId,
|
|
297
|
+
path: persisted.path,
|
|
298
|
+
stage: persisted.stage,
|
|
299
|
+
stage_n: persisted.stageN,
|
|
300
|
+
sha256: persisted.sha256,
|
|
301
|
+
created_at: persisted.createdAt,
|
|
302
|
+
};
|
|
303
|
+
if (persisted.pendingApprovalPath) payload.pending_approval_path = persisted.pendingApprovalPath;
|
|
304
|
+
const stdout = resolved.json
|
|
305
|
+
? `${JSON.stringify(payload, null, 2)}\n`
|
|
306
|
+
: `Persisted ralplan ${persisted.stage} stage ${persisted.stageN} at ${persisted.path}.\n`;
|
|
307
|
+
return { status: 0, stdout };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/* -------------------------------- handoff -------------------------------- */
|
|
311
|
+
|
|
312
|
+
interface ConsensusHandoffArgs {
|
|
313
|
+
interactive: boolean;
|
|
314
|
+
deliberate: boolean;
|
|
315
|
+
architectKind?: string;
|
|
316
|
+
criticKind?: string;
|
|
317
|
+
sessionId?: string;
|
|
318
|
+
task: string;
|
|
319
|
+
json: boolean;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function extractPositionalTask(args: readonly string[]): string {
|
|
323
|
+
const parts: string[] = [];
|
|
324
|
+
let skipNext = false;
|
|
325
|
+
for (const arg of args) {
|
|
326
|
+
if (skipNext) {
|
|
327
|
+
skipNext = false;
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (VALUE_FLAGS.has(arg)) {
|
|
331
|
+
skipNext = true;
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
if (arg === "--interactive" || arg === "--deliberate" || arg === "--write" || arg === "--json") continue;
|
|
335
|
+
if (arg.startsWith("-")) {
|
|
336
|
+
throw new RalplanCommandError(2, `unknown flag for gjc ralplan: ${arg}`);
|
|
337
|
+
}
|
|
338
|
+
parts.push(arg);
|
|
339
|
+
}
|
|
340
|
+
return parts.join(" ").trim();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function resolveConsensusArgs(args: readonly string[]): ConsensusHandoffArgs {
|
|
344
|
+
const architectKind = flagValue(args, "--architect")?.trim() || undefined;
|
|
345
|
+
if (architectKind && !KNOWN_ARCHITECT_KINDS.has(architectKind)) {
|
|
346
|
+
throw new RalplanCommandError(
|
|
347
|
+
2,
|
|
348
|
+
`unknown --architect kind: ${architectKind}. Expected one of: ${[...KNOWN_ARCHITECT_KINDS].join(", ")}.`,
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
const criticKind = flagValue(args, "--critic")?.trim() || undefined;
|
|
352
|
+
if (criticKind && !KNOWN_CRITIC_KINDS.has(criticKind)) {
|
|
353
|
+
throw new RalplanCommandError(
|
|
354
|
+
2,
|
|
355
|
+
`unknown --critic kind: ${criticKind}. Expected one of: ${[...KNOWN_CRITIC_KINDS].join(", ")}.`,
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
const sessionId = flagValue(args, "--session-id")?.trim() || undefined;
|
|
359
|
+
if (sessionId) assertSafePathComponent(sessionId, "session-id");
|
|
360
|
+
const task = extractPositionalTask(args);
|
|
361
|
+
return {
|
|
362
|
+
interactive: hasFlag(args, "--interactive"),
|
|
363
|
+
deliberate: hasFlag(args, "--deliberate"),
|
|
364
|
+
architectKind,
|
|
365
|
+
criticKind,
|
|
366
|
+
sessionId,
|
|
367
|
+
task,
|
|
368
|
+
json: hasFlag(args, "--json"),
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function encodeSessionSegment(value: string): string {
|
|
373
|
+
return encodeURIComponent(value).replaceAll(".", "%2E");
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
async function seedRalplanState(
|
|
377
|
+
cwd: string,
|
|
378
|
+
resolved: ConsensusHandoffArgs,
|
|
379
|
+
): Promise<{ statePath: string; runId: string }> {
|
|
380
|
+
const stateDir = resolved.sessionId
|
|
381
|
+
? path.join(cwd, ".gjc", "state", "sessions", encodeSessionSegment(resolved.sessionId))
|
|
382
|
+
: path.join(cwd, ".gjc", "state");
|
|
383
|
+
await fs.mkdir(stateDir, { recursive: true });
|
|
384
|
+
const statePath = path.join(stateDir, "ralplan-state.json");
|
|
385
|
+
// Reuse an existing run id when present so a re-invocation of `gjc ralplan "task"` doesn't
|
|
386
|
+
// orphan in-progress artifacts under a fresh run id.
|
|
387
|
+
const existingRunId = await readActiveRunId(cwd, resolved.sessionId);
|
|
388
|
+
const runId = existingRunId ?? resolved.sessionId ?? defaultRunId();
|
|
389
|
+
assertSafePathComponent(runId, "run-id");
|
|
390
|
+
const now = new Date().toISOString();
|
|
391
|
+
const payload: Record<string, unknown> = {
|
|
392
|
+
active: true,
|
|
393
|
+
current_phase: "planner",
|
|
394
|
+
skill: "ralplan",
|
|
395
|
+
mode: resolved.deliberate ? "deliberate" : "short",
|
|
396
|
+
interactive: resolved.interactive,
|
|
397
|
+
task: resolved.task,
|
|
398
|
+
run_id: runId,
|
|
399
|
+
updated_at: now,
|
|
400
|
+
};
|
|
401
|
+
if (resolved.architectKind) payload.architect_kind = resolved.architectKind;
|
|
402
|
+
if (resolved.criticKind) payload.critic_kind = resolved.criticKind;
|
|
403
|
+
if (resolved.sessionId) payload.session_id = resolved.sessionId;
|
|
404
|
+
await fs.writeFile(statePath, `${JSON.stringify(payload, null, 2)}\n`);
|
|
405
|
+
return { statePath, runId };
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async function handleConsensusHandoff(args: readonly string[], cwd: string): Promise<RalplanCommandResult> {
|
|
409
|
+
const resolved = resolveConsensusArgs(args);
|
|
410
|
+
if (!resolved.task) {
|
|
411
|
+
throw new RalplanCommandError(2, 'gjc ralplan requires a task description, e.g. `gjc ralplan "<task>"`.');
|
|
412
|
+
}
|
|
413
|
+
const { statePath, runId } = await seedRalplanState(cwd, resolved);
|
|
414
|
+
const mode = resolved.deliberate ? "deliberate" : "short";
|
|
415
|
+
await syncRalplanHud({
|
|
416
|
+
cwd,
|
|
417
|
+
sessionId: resolved.sessionId,
|
|
418
|
+
stage: "planner",
|
|
419
|
+
pendingApproval: false,
|
|
420
|
+
iteration: 1,
|
|
421
|
+
latestSummary: `${mode} run · ${resolved.interactive ? "interactive" : "automated"}`,
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
const summary = {
|
|
425
|
+
skill: "ralplan",
|
|
426
|
+
mode,
|
|
427
|
+
interactive: resolved.interactive,
|
|
428
|
+
architect: resolved.architectKind ?? "default",
|
|
429
|
+
critic: resolved.criticKind ?? "default",
|
|
430
|
+
task: resolved.task,
|
|
431
|
+
state_path: statePath,
|
|
432
|
+
run_id: runId,
|
|
433
|
+
handoff: "Run `/skill:ralplan` inside the GJC agent to drive the Planner / Architect / Critic consensus loop.",
|
|
434
|
+
};
|
|
435
|
+
const stdout = resolved.json
|
|
436
|
+
? `${JSON.stringify(summary, null, 2)}\n`
|
|
437
|
+
: [
|
|
438
|
+
`Seeded ralplan ${summary.mode} run (${resolved.interactive ? "interactive" : "automated"}) at ${statePath}.`,
|
|
439
|
+
`Active run_id: ${runId}`,
|
|
440
|
+
resolved.architectKind ? `Architect: ${resolved.architectKind}` : undefined,
|
|
441
|
+
resolved.criticKind ? `Critic: ${resolved.criticKind}` : undefined,
|
|
442
|
+
"Run `/skill:ralplan` inside the GJC agent to execute the consensus loop.",
|
|
443
|
+
"",
|
|
444
|
+
]
|
|
445
|
+
.filter((line): line is string => Boolean(line))
|
|
446
|
+
.join("\n");
|
|
447
|
+
return { status: 0, stdout };
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/* -------------------------------- entry --------------------------------- */
|
|
451
|
+
|
|
452
|
+
export async function runNativeRalplanCommand(args: string[], cwd = process.cwd()): Promise<RalplanCommandResult> {
|
|
453
|
+
try {
|
|
454
|
+
if (isRalplanArtifactWriteInvocation(args)) return await handleArtifactWrite(args, cwd);
|
|
455
|
+
return await handleConsensusHandoff(args, cwd);
|
|
456
|
+
} catch (error) {
|
|
457
|
+
if (error instanceof RalplanCommandError) return { status: error.exitStatus, stderr: `${error.message}\n` };
|
|
458
|
+
return { status: 1, stderr: `${error instanceof Error ? error.message : String(error)}\n` };
|
|
459
|
+
}
|
|
460
|
+
}
|