@gajae-code/coding-agent 0.1.3 → 0.2.0

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 CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.2.0] - 2026-05-28
6
+
7
+ ### Added
8
+
9
+ - Added scoped GJC tmux profile handling for `gjc --tmux` and `gjc team` sessions without mutating global tmux configuration.
10
+ - Added GJC team integration hardening for worker turn-end integration requests, auto-rebase/auto-merge conflict surfacing, protected checkpoint classification, and leader/worker-visible integration summaries.
11
+ - Added Node 20 release baseline validation to the release/check surface.
12
+
13
+ ### Changed
14
+
15
+ - Clarified the public workflow contract so `deep-interview` and `ralplan` are invoked through `/skill:<name>`, while `gjc ultragoal` and `gjc team` remain native runtime commands.
16
+ - Updated the README hero image and Discord community invite.
17
+
18
+ ### Fixed
19
+
20
+ - Restored Ultragoal completion receipt export/generation validation and completion gates.
21
+ - Fixed workflow bridge guidance and tests so private compatibility bridge commands are not advertised as public skill-loading paths.
22
+
5
23
  ## [0.1.3] - 2026-05-28
6
24
 
7
25
  ### Changed
@@ -1,6 +1,30 @@
1
+ import { type WorkflowHudSummary } from "../skill-state/active-state";
2
+ export declare const WORKFLOW_HUD_PROTOCOL = "workflow-hud-summary-v1";
1
3
  export interface GjcRuntimeBridgeResult {
2
4
  status: number;
3
5
  error?: string;
4
6
  }
7
+ export interface WorkflowHudBridgePayload {
8
+ version: 1;
9
+ skill: string;
10
+ phase?: string;
11
+ active?: boolean;
12
+ session_id?: string;
13
+ thread_id?: string;
14
+ turn_id?: string;
15
+ hud: WorkflowHudSummary;
16
+ }
17
+ export interface GjcRuntimeHudBridgeResult extends GjcRuntimeBridgeResult {
18
+ hudPayload?: WorkflowHudBridgePayload;
19
+ }
20
+ export interface GjcRuntimeHudBridgeOptions {
21
+ cwd?: string;
22
+ env?: NodeJS.ProcessEnv;
23
+ sidecarSkill: string;
24
+ onHudPayload?: (payload: WorkflowHudBridgePayload) => Promise<void> | void;
25
+ pollIntervalMs?: number;
26
+ }
27
+ export declare function normalizeWorkflowHudBridgePayload(raw: unknown, expectedSkill: string): WorkflowHudBridgePayload | null;
5
28
  export declare function runGjcRuntimeBridge(endpoint: string, args: string[], env?: NodeJS.ProcessEnv): GjcRuntimeBridgeResult;
29
+ export declare function runGjcRuntimeBridgeWithHudSidecar(endpoint: string, args: string[], options: GjcRuntimeHudBridgeOptions): Promise<GjcRuntimeHudBridgeResult>;
6
30
  export declare function runBridgedRuntimeEndpoint(endpoint: string, args: string[]): Promise<void>;
@@ -3,6 +3,8 @@ export declare const GJC_DEFAULT_TMUX_SESSION = "gajae_code";
3
3
  export declare const GJC_TMUX_LAUNCHED_ENV = "GJC_TMUX_LAUNCHED";
4
4
  export declare const GJC_LAUNCH_POLICY_ENV = "GJC_LAUNCH_POLICY";
5
5
  export declare const GJC_TMUX_COMMAND_ENV = "GJC_TMUX_COMMAND";
6
+ export declare const GJC_TMUX_PROFILE_ENV = "GJC_TMUX_PROFILE";
7
+ export declare const GJC_TMUX_MOUSE_ENV = "GJC_MOUSE";
6
8
  interface TtyState {
7
9
  stdin: boolean;
8
10
  stdout: boolean;
@@ -40,6 +42,27 @@ export interface TmuxLaunchPlan {
40
42
  newSessionArgs: string[];
41
43
  attachSessionArgs: string[];
42
44
  }
45
+ export interface GjcTmuxProfileCommand {
46
+ description: string;
47
+ args: string[];
48
+ }
49
+ export interface GjcTmuxProfileResult {
50
+ skipped: boolean;
51
+ commands: GjcTmuxProfileCommand[];
52
+ failures: Array<{
53
+ command: GjcTmuxProfileCommand;
54
+ stderr?: string;
55
+ }>;
56
+ }
57
+ export interface GjcTmuxProfileContext {
58
+ tmuxCommand: string;
59
+ target: string;
60
+ cwd?: string;
61
+ env?: NodeJS.ProcessEnv;
62
+ spawnSync?: TmuxSpawnSync;
63
+ }
64
+ export declare function buildGjcTmuxProfileCommands(target: string, env?: NodeJS.ProcessEnv): GjcTmuxProfileCommand[];
65
+ export declare function applyGjcTmuxProfile(context: GjcTmuxProfileContext): GjcTmuxProfileResult;
43
66
  export declare function buildDefaultTmuxLaunchPlan(context: TmuxLaunchContext): TmuxLaunchPlan | undefined;
44
67
  export declare function launchDefaultTmuxIfNeeded(context: TmuxLaunchContext): boolean;
45
68
  export {};
@@ -1,3 +1,4 @@
1
+ import type { WorkflowHudSummary } from "../skill-state/active-state";
1
2
  export type GjcTeamPhase = "starting" | "running" | "complete" | "failed" | "cancelled";
2
3
  export type GjcTeamTaskStatus = "pending" | "blocked" | "in_progress" | "completed" | "failed";
3
4
  export type GjcWorkerStatusState = "idle" | "working" | "blocked" | "done" | "failed" | "draining" | "unknown";
@@ -143,7 +144,7 @@ export interface GjcTeamMailboxMessage {
143
144
  export declare function resolveGjcTeamWorkerCli(env?: NodeJS.ProcessEnv): GjcTeamWorkerCli;
144
145
  export declare function resolveGjcTeamWorkerCliPlan(workerCount: number, env?: NodeJS.ProcessEnv): GjcTeamWorkerCli[];
145
146
  export declare function translateGjcWorkerLaunchArgsForCli(workerCli: GjcTeamWorkerCli, args: string[]): string[];
146
- interface GjcTeamEvent {
147
+ export interface GjcTeamEvent {
147
148
  event_id: string;
148
149
  ts: string;
149
150
  type: string;
@@ -164,12 +165,45 @@ interface WorkerHeartbeatFile {
164
165
  turn_count: number;
165
166
  alive: boolean;
166
167
  }
168
+ export interface GjcWorkerIntegrationAttemptRequestResult {
169
+ requested: boolean;
170
+ reason: "requested" | "not_worker" | "missing_worktree" | "no_changes" | "deduped" | "git_error";
171
+ worker?: string;
172
+ team_name?: string;
173
+ fingerprint?: string;
174
+ head?: string | null;
175
+ status?: GjcWorkerCheckpointClassification["kind"];
176
+ }
167
177
  export declare const GJC_TEAM_API_OPERATIONS: readonly ["send-message", "broadcast", "mailbox-list", "mailbox-mark-delivered", "mailbox-mark-notified", "create-task", "read-task", "list-tasks", "update-task", "claim-task", "transition-task-status", "transition-task", "release-task-claim", "read-config", "read-manifest", "read-worker-status", "read-worker-heartbeat", "update-worker-heartbeat", "write-worker-inbox", "write-worker-identity", "append-event", "read-events", "await-event", "write-shutdown-request", "read-shutdown-ack", "read-monitor-snapshot", "write-monitor-snapshot", "read-task-approval", "write-task-approval"];
168
178
  export declare function resolveGjcTeamStateRoot(cwd?: string, env?: NodeJS.ProcessEnv): string;
169
179
  export declare function resolveGjcTmuxCommand(env?: NodeJS.ProcessEnv): string;
170
180
  export declare function resolveGjcWorkerCommand(cwd?: string, env?: NodeJS.ProcessEnv): string;
181
+ export type GjcWorkerCheckpointClassification = {
182
+ kind: "clean";
183
+ files: string[];
184
+ } | {
185
+ kind: "eligible";
186
+ files: string[];
187
+ } | {
188
+ kind: "protected_only";
189
+ files: string[];
190
+ } | {
191
+ kind: "conflicted";
192
+ files: string[];
193
+ } | {
194
+ kind: "git_error";
195
+ files: string[];
196
+ detail: string;
197
+ };
198
+ export declare function classifyGjcTeamCheckpointFiles(files: string[]): {
199
+ eligible: string[];
200
+ protected: string[];
201
+ };
202
+ export declare function classifyWorkerCheckpointStatus(cwd: string): GjcWorkerCheckpointClassification;
171
203
  export declare function startGjcTeam(options: GjcTeamStartOptions): Promise<GjcTeamSnapshot>;
172
204
  export declare function readGjcTeamSnapshot(teamName: string, cwd?: string, env?: NodeJS.ProcessEnv): Promise<GjcTeamSnapshot>;
205
+ export declare function requestGjcWorkerIntegrationAttempt(cwd?: string, env?: NodeJS.ProcessEnv): Promise<GjcWorkerIntegrationAttemptRequestResult>;
206
+ export declare function buildTeamHudSummary(snapshot: GjcTeamSnapshot, latestEvent?: GjcTeamEvent, latestMessage?: GjcTeamMailboxMessage): Promise<WorkflowHudSummary>;
173
207
  export declare function monitorGjcTeam(teamName: string, cwd?: string, env?: NodeJS.ProcessEnv): Promise<GjcTeamSnapshot>;
174
208
  export declare function listGjcTeams(cwd?: string, env?: NodeJS.ProcessEnv): Promise<GjcTeamSnapshot[]>;
175
209
  export declare function shutdownGjcTeam(teamName: string, cwd?: string, env?: NodeJS.ProcessEnv): Promise<GjcTeamSnapshot>;
@@ -1,3 +1,4 @@
1
+ import type { WorkflowHudSummary } from "../skill-state/active-state";
1
2
  export type UltragoalGjcGoalMode = "aggregate" | "per-story";
2
3
  export type UltragoalGoalStatus = "pending" | "active" | "complete" | "failed" | "blocked" | "review_blocked" | "superseded";
3
4
  export interface UltragoalGoal {
@@ -34,6 +35,7 @@ export interface UltragoalCompletionVerification {
34
35
  gjcGoalMode: UltragoalGjcGoalMode;
35
36
  gjcObjective: string;
36
37
  qualityGateHash: string;
38
+ gjcGoalSnapshotHash: string;
37
39
  planGeneration: string;
38
40
  basis: {
39
41
  planHashBeforeCheckpoint: string;
@@ -77,8 +79,21 @@ interface JsonObject {
77
79
  export declare function hashStructuredValue(value: unknown): string;
78
80
  export declare function getUltragoalPaths(cwd: string): UltragoalPaths;
79
81
  export declare function readUltragoalLedger(cwd: string): Promise<UltragoalLedgerEvent[]>;
82
+ export declare function computeUltragoalPlanGeneration(input: {
83
+ plan: UltragoalPlan;
84
+ ledger: readonly UltragoalLedgerEvent[];
85
+ goal: UltragoalGoal;
86
+ receiptKind: UltragoalReceiptKind;
87
+ beforeStatus: UltragoalGoalStatus;
88
+ excludeEventId?: string;
89
+ targetGoalUpdatedAt?: string;
90
+ }): {
91
+ planGeneration: string;
92
+ basis: UltragoalCompletionVerification["basis"];
93
+ };
80
94
  export declare function readUltragoalPlan(cwd: string): Promise<UltragoalPlan | null>;
81
95
  export declare function getUltragoalStatus(cwd: string): Promise<UltragoalStatusSummary>;
96
+ export declare function buildUltragoalHudSummary(summary: UltragoalStatusSummary, latestLedger?: UltragoalLedgerEvent): WorkflowHudSummary;
82
97
  export declare function createUltragoalPlan(input: {
83
98
  cwd: string;
84
99
  brief: string;
@@ -92,16 +107,6 @@ export declare function startNextUltragoalGoal(input: {
92
107
  goal?: UltragoalGoal;
93
108
  allComplete: boolean;
94
109
  }>;
95
- export declare function computeUltragoalPlanGeneration(input: {
96
- plan: UltragoalPlan;
97
- ledger: readonly UltragoalLedgerEvent[];
98
- goal: UltragoalGoal;
99
- receiptKind: UltragoalReceiptKind;
100
- beforeStatus: UltragoalGoalStatus;
101
- excludeEventId?: string;
102
- }): UltragoalCompletionVerification["basis"] & {
103
- planGeneration: string;
104
- };
105
110
  export declare function checkpointUltragoalGoal(input: {
106
111
  cwd: string;
107
112
  goalId: string;
@@ -1,4 +1,5 @@
1
1
  import type { SkillDiscoverySettings } from "../config/skill-settings-defaults";
2
+ import type { SkillActiveEntry as CanonicalSkillActiveEntry, WorkflowHudSummary } from "../skill-state/active-state";
2
3
  import { type GjcWorkflowSkill } from "./skill-keywords";
3
4
  export declare const GJC_STATE_DIR = ".gjc/state";
4
5
  export declare const SKILL_ACTIVE_STATE_FILE = "skill-active-state.json";
@@ -13,7 +14,7 @@ export interface SkillKeywordMatch {
13
14
  skill: GjcWorkflowSkill;
14
15
  priority: number;
15
16
  }
16
- export interface SkillActiveEntry {
17
+ export interface SkillActiveEntry extends Omit<CanonicalSkillActiveEntry, "skill"> {
17
18
  skill: GjcWorkflowSkill;
18
19
  phase?: string;
19
20
  active?: boolean;
@@ -22,6 +23,8 @@ export interface SkillActiveEntry {
22
23
  session_id?: string;
23
24
  thread_id?: string;
24
25
  turn_id?: string;
26
+ hud?: WorkflowHudSummary;
27
+ stale?: boolean;
25
28
  }
26
29
  export interface SkillActiveState {
27
30
  version: number;
@@ -2,6 +2,21 @@ export declare const SKILL_ACTIVE_STATE_FILE = "skill-active-state.json";
2
2
  export declare const SKILL_ACTIVE_STALE_MS: number;
3
3
  export declare const CANONICAL_GJC_WORKFLOW_SKILLS: readonly ["deep-interview", "ralplan", "ultragoal", "team"];
4
4
  export type CanonicalGjcWorkflowSkill = (typeof CANONICAL_GJC_WORKFLOW_SKILLS)[number];
5
+ export type WorkflowHudSeverity = "info" | "warning" | "blocked" | "error" | "success";
6
+ export interface WorkflowHudChip {
7
+ label: string;
8
+ value?: string;
9
+ priority?: number;
10
+ severity?: WorkflowHudSeverity;
11
+ }
12
+ export interface WorkflowHudSummary {
13
+ version: 1;
14
+ summary?: string;
15
+ chips?: WorkflowHudChip[];
16
+ details?: WorkflowHudChip[];
17
+ severity?: WorkflowHudSeverity;
18
+ updated_at?: string;
19
+ }
5
20
  export interface SkillActiveEntry {
6
21
  skill: string;
7
22
  phase?: string;
@@ -11,6 +26,8 @@ export interface SkillActiveEntry {
11
26
  session_id?: string;
12
27
  thread_id?: string;
13
28
  turn_id?: string;
29
+ hud?: WorkflowHudSummary;
30
+ stale?: boolean;
14
31
  }
15
32
  export interface SkillActiveState {
16
33
  version?: number;
@@ -41,7 +58,9 @@ export interface SyncSkillActiveStateOptions {
41
58
  turnId?: string;
42
59
  nowIso?: string;
43
60
  source?: string;
61
+ hud?: WorkflowHudSummary;
44
62
  }
63
+ export declare function normalizeWorkflowHudSummary(raw: unknown): WorkflowHudSummary | undefined;
45
64
  export declare function isCanonicalGjcWorkflowSkill(skill: string): skill is CanonicalGjcWorkflowSkill;
46
65
  export declare function listActiveSkills(raw: unknown): SkillActiveEntry[];
47
66
  export declare function normalizeSkillActiveState(raw: unknown): SkillActiveState | null;
@@ -0,0 +1,62 @@
1
+ import type { WorkflowHudSummary } from "./active-state";
2
+ interface DeepInterviewHudState {
3
+ phase?: string;
4
+ ambiguity?: number;
5
+ threshold?: number;
6
+ roundCount?: number;
7
+ targetComponent?: string;
8
+ weakestDimension?: string;
9
+ specStatus?: string;
10
+ updatedAt?: string;
11
+ }
12
+ interface RalplanHudState {
13
+ stage?: string;
14
+ waiting?: string;
15
+ iteration?: number;
16
+ verdict?: string;
17
+ latestSummary?: string;
18
+ pendingApproval?: boolean;
19
+ updatedAt?: string;
20
+ }
21
+ interface UltragoalLikeGoal {
22
+ id: string;
23
+ title: string;
24
+ status: string;
25
+ }
26
+ interface UltragoalHudState {
27
+ status: string;
28
+ currentGoal?: UltragoalLikeGoal;
29
+ counts: Record<string, number>;
30
+ goals: UltragoalLikeGoal[];
31
+ latestLedgerEvent?: {
32
+ event?: string;
33
+ goalId?: string;
34
+ timestamp?: string;
35
+ };
36
+ updatedAt?: string;
37
+ }
38
+ interface TeamHudWorker {
39
+ id: string;
40
+ status?: string;
41
+ }
42
+ interface TeamHudState {
43
+ phase: string;
44
+ task_total: number;
45
+ task_counts: Record<string, number>;
46
+ workers: TeamHudWorker[];
47
+ updated_at?: string;
48
+ latestEvent?: {
49
+ type?: string;
50
+ worker?: string;
51
+ message?: string;
52
+ };
53
+ latestMessage?: {
54
+ from_worker?: string;
55
+ body?: string;
56
+ };
57
+ }
58
+ export declare function buildDeepInterviewHudSummary(state: DeepInterviewHudState): WorkflowHudSummary;
59
+ export declare function buildRalplanHudSummary(state: RalplanHudState): WorkflowHudSummary;
60
+ export declare function buildUltragoalHudSummary(state: UltragoalHudState): WorkflowHudSummary;
61
+ export declare function buildTeamHudSummary(state: TeamHudState): WorkflowHudSummary;
62
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/coding-agent",
4
- "version": "0.1.3",
4
+ "version": "0.2.0",
5
5
  "description": "Gajae Code CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://gaebal-gajae.dev",
7
7
  "author": "Yeachan-Heo",
@@ -48,12 +48,12 @@
48
48
  "@agentclientprotocol/sdk": "0.21.0",
49
49
  "@babel/parser": "^7.29.3",
50
50
  "@mozilla/readability": "^0.6.0",
51
- "@gajae-code/stats": "0.1.3",
52
- "@gajae-code/agent-core": "0.1.3",
53
- "@gajae-code/ai": "0.1.3",
54
- "@gajae-code/natives": "0.1.3",
55
- "@gajae-code/tui": "0.1.3",
56
- "@gajae-code/utils": "0.1.3",
51
+ "@gajae-code/stats": "0.2.0",
52
+ "@gajae-code/agent-core": "0.2.0",
53
+ "@gajae-code/ai": "0.2.0",
54
+ "@gajae-code/natives": "0.2.0",
55
+ "@gajae-code/tui": "0.2.0",
56
+ "@gajae-code/utils": "0.2.0",
57
57
  "@puppeteer/browsers": "^2.13.0",
58
58
  "@types/turndown": "5.0.6",
59
59
  "@xterm/headless": "^6.0.0",
@@ -1,5 +1,6 @@
1
1
  import { Command } from "@gajae-code/utils/cli";
2
- import { runBridgedRuntimeEndpoint } from "./gjc-runtime-bridge";
2
+ import { syncSkillActiveState } from "../skill-state/active-state";
3
+ import { runGjcRuntimeBridgeWithHudSidecar } from "./gjc-runtime-bridge";
3
4
 
4
5
  export default class DeepInterview extends Command {
5
6
  static description = "Run private GJC deep-interview workflow commands";
@@ -7,6 +8,24 @@ export default class DeepInterview extends Command {
7
8
  static examples = ["$ gjc deep-interview --help"];
8
9
 
9
10
  async run(): Promise<void> {
10
- await runBridgedRuntimeEndpoint("deep-interview", this.argv);
11
+ const cwd = process.cwd();
12
+ const result = await runGjcRuntimeBridgeWithHudSidecar("deep-interview", this.argv, {
13
+ cwd,
14
+ sidecarSkill: "deep-interview",
15
+ onHudPayload: payload =>
16
+ syncSkillActiveState({
17
+ cwd,
18
+ skill: "deep-interview",
19
+ active: payload.active ?? true,
20
+ phase: payload.phase,
21
+ sessionId: payload.session_id,
22
+ threadId: payload.thread_id,
23
+ turnId: payload.turn_id,
24
+ hud: payload.hud,
25
+ source: "gjc-runtime-bridge",
26
+ }),
27
+ });
28
+ if (result.error) process.stderr.write(`${result.error}\n`);
29
+ process.exitCode = result.status;
11
30
  }
12
31
  }
@@ -1,15 +1,46 @@
1
- import { spawnSync } from "node:child_process";
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { randomUUID } from "node:crypto";
2
3
  import { existsSync } from "node:fs";
4
+ import * as fs from "node:fs/promises";
5
+ import * as os from "node:os";
6
+ import * as path from "node:path";
7
+ import { normalizeWorkflowHudSummary, type WorkflowHudSummary } from "../skill-state/active-state";
3
8
 
4
9
  const BRIDGE_ENV = "GJC_RUNTIME_BINARY";
5
10
  const LEGACY_BRIDGE_ENV = "GJC_LEGACY_RUNTIME_BINARY";
6
11
  const GUARD_ENV = "GJC_RUNTIME_BRIDGE_ACTIVE";
12
+ export const WORKFLOW_HUD_PROTOCOL = "workflow-hud-summary-v1";
7
13
 
8
14
  export interface GjcRuntimeBridgeResult {
9
15
  status: number;
10
16
  error?: string;
11
17
  }
12
18
 
19
+ const SKILL_ENTRYPOINT_ENDPOINTS = new Set(["deep-interview", "ralplan"]);
20
+
21
+ export interface WorkflowHudBridgePayload {
22
+ version: 1;
23
+ skill: string;
24
+ phase?: string;
25
+ active?: boolean;
26
+ session_id?: string;
27
+ thread_id?: string;
28
+ turn_id?: string;
29
+ hud: WorkflowHudSummary;
30
+ }
31
+
32
+ export interface GjcRuntimeHudBridgeResult extends GjcRuntimeBridgeResult {
33
+ hudPayload?: WorkflowHudBridgePayload;
34
+ }
35
+
36
+ export interface GjcRuntimeHudBridgeOptions {
37
+ cwd?: string;
38
+ env?: NodeJS.ProcessEnv;
39
+ sidecarSkill: string;
40
+ onHudPayload?: (payload: WorkflowHudBridgePayload) => Promise<void> | void;
41
+ pollIntervalMs?: number;
42
+ }
43
+
13
44
  function candidateBinaries(env: NodeJS.ProcessEnv): string[] {
14
45
  return [env[BRIDGE_ENV], env[LEGACY_BRIDGE_ENV]].filter(
15
46
  (value): value is string => typeof value === "string" && value.trim().length > 0,
@@ -24,6 +55,61 @@ function canAttempt(command: string): boolean {
24
55
  return !isPathLike(command) || existsSync(command);
25
56
  }
26
57
 
58
+ function unavailableBridgeResult(
59
+ endpoint: string,
60
+ env: NodeJS.ProcessEnv,
61
+ attempted: string[],
62
+ ): GjcRuntimeBridgeResult {
63
+ const configured = [env[BRIDGE_ENV], env[LEGACY_BRIDGE_ENV]].filter(Boolean).join(", ");
64
+ const guidance = SKILL_ENTRYPOINT_ENDPOINTS.has(endpoint)
65
+ ? `Inside a GJC agent session, invoke /skill:${endpoint} instead so the bundled skill is loaded directly.`
66
+ : `Configure ${BRIDGE_ENV} with a GJC-compatible private runtime binary for the ${endpoint} endpoint.`;
67
+ return {
68
+ status: 1,
69
+ error: [
70
+ `gjc ${endpoint} is a private runtime bridge command.`,
71
+ guidance,
72
+ `Only private runtime deployments should call this bridge command; configure them with ${BRIDGE_ENV}.`,
73
+ configured
74
+ ? `Configured runtime candidates failed: ${configured}.`
75
+ : "No private GJC runtime binary was configured.",
76
+ attempted.length > 0 ? `Attempted: ${attempted.join(", ")}.` : undefined,
77
+ ]
78
+ .filter(Boolean)
79
+ .join("\n"),
80
+ };
81
+ }
82
+
83
+ export function normalizeWorkflowHudBridgePayload(
84
+ raw: unknown,
85
+ expectedSkill: string,
86
+ ): WorkflowHudBridgePayload | null {
87
+ if (!raw || typeof raw !== "object") return null;
88
+ const record = raw as Record<string, unknown>;
89
+ if (record.version !== 1 || record.skill !== expectedSkill) return null;
90
+ const hud = normalizeWorkflowHudSummary(record.hud);
91
+ if (!hud) return null;
92
+ return {
93
+ version: 1,
94
+ skill: expectedSkill,
95
+ phase: typeof record.phase === "string" && record.phase.trim() ? record.phase.trim() : undefined,
96
+ active: typeof record.active === "boolean" ? record.active : undefined,
97
+ session_id:
98
+ typeof record.session_id === "string" && record.session_id.trim() ? record.session_id.trim() : undefined,
99
+ thread_id: typeof record.thread_id === "string" && record.thread_id.trim() ? record.thread_id.trim() : undefined,
100
+ turn_id: typeof record.turn_id === "string" && record.turn_id.trim() ? record.turn_id.trim() : undefined,
101
+ hud,
102
+ };
103
+ }
104
+
105
+ async function readHudPayload(sidecarPath: string, expectedSkill: string): Promise<WorkflowHudBridgePayload | null> {
106
+ try {
107
+ return normalizeWorkflowHudBridgePayload(JSON.parse(await Bun.file(sidecarPath).text()), expectedSkill);
108
+ } catch {
109
+ return null;
110
+ }
111
+ }
112
+
27
113
  export function runGjcRuntimeBridge(
28
114
  endpoint: string,
29
115
  args: string[],
@@ -58,20 +144,80 @@ export function runGjcRuntimeBridge(
58
144
  return { status: child.status ?? (child.signal ? 1 : 0) };
59
145
  }
60
146
 
61
- const configured = [env[BRIDGE_ENV], env[LEGACY_BRIDGE_ENV]].filter(Boolean).join(", ");
62
- return {
63
- status: 1,
64
- error: [
65
- `gjc ${endpoint} requires the private GJC runtime endpoint implementation.`,
66
- `Set ${BRIDGE_ENV} to a GJC-compatible runtime binary.`,
67
- configured
68
- ? `Configured runtime candidates failed: ${configured}.`
69
- : "No gjc runtime binary was found on PATH.",
70
- attempted.length > 0 ? `Attempted: ${attempted.join(", ")}.` : undefined,
71
- ]
72
- .filter(Boolean)
73
- .join("\n"),
74
- };
147
+ return unavailableBridgeResult(endpoint, env, attempted);
148
+ }
149
+
150
+ export async function runGjcRuntimeBridgeWithHudSidecar(
151
+ endpoint: string,
152
+ args: string[],
153
+ options: GjcRuntimeHudBridgeOptions,
154
+ ): Promise<GjcRuntimeHudBridgeResult> {
155
+ const env = options.env ?? process.env;
156
+ if (env[GUARD_ENV] === "1") return { status: 1, error: `Refusing recursive gjc runtime bridge for ${endpoint}.` };
157
+
158
+ const attempted: string[] = [];
159
+ for (const binary of candidateBinaries(env)) {
160
+ const command = binary.trim();
161
+ if (!canAttempt(command)) continue;
162
+ attempted.push(command);
163
+ const sidecarDir = await fs.mkdtemp(path.join(os.tmpdir(), "gjc-workflow-hud-"));
164
+ const sidecarPath = path.join(sidecarDir, `${options.sidecarSkill}-${randomUUID()}.json`);
165
+ let latestPayload: WorkflowHudBridgePayload | undefined;
166
+ let lastRaw = "";
167
+ const publishPayload = async (): Promise<void> => {
168
+ let raw = "";
169
+ try {
170
+ raw = await Bun.file(sidecarPath).text();
171
+ } catch {
172
+ return;
173
+ }
174
+ if (!raw || raw === lastRaw) return;
175
+ lastRaw = raw;
176
+ const payload = await readHudPayload(sidecarPath, options.sidecarSkill);
177
+ if (!payload) return;
178
+ latestPayload = payload;
179
+ try {
180
+ await options.onHudPayload?.(payload);
181
+ } catch {
182
+ // HUD sidecar sync is best-effort and must not change child command semantics.
183
+ }
184
+ };
185
+ try {
186
+ const child = spawn(command, [endpoint, ...args], {
187
+ cwd: options.cwd,
188
+ stdio: "inherit",
189
+ env: {
190
+ ...env,
191
+ [GUARD_ENV]: "1",
192
+ GJC_WORKFLOW_HUD_PROTOCOL: WORKFLOW_HUD_PROTOCOL,
193
+ GJC_WORKFLOW_HUD_SIDECAR: sidecarPath,
194
+ GJC_WORKFLOW_HUD_SKILL: options.sidecarSkill,
195
+ },
196
+ });
197
+ const interval = setInterval(() => {
198
+ void publishPayload();
199
+ }, options.pollIntervalMs ?? 100);
200
+ const exit = Promise.withResolvers<{ status: number; error?: string; code?: string }>();
201
+ child.on("error", error => {
202
+ const fsError = error as NodeJS.ErrnoException;
203
+ exit.resolve({ status: 1, error: error.message, code: fsError.code });
204
+ });
205
+ child.on("exit", (code, signal) => exit.resolve({ status: code ?? (signal ? 1 : 0) }));
206
+ const result = await exit.promise;
207
+ clearInterval(interval);
208
+ await publishPayload();
209
+ if (result.code === "ENOENT") continue;
210
+ return {
211
+ status: result.status,
212
+ ...(result.error ? { error: result.error } : {}),
213
+ ...(latestPayload ? { hudPayload: latestPayload } : {}),
214
+ };
215
+ } finally {
216
+ await fs.rm(sidecarDir, { recursive: true, force: true });
217
+ }
218
+ }
219
+
220
+ return unavailableBridgeResult(endpoint, env, attempted);
75
221
  }
76
222
 
77
223
  export async function runBridgedRuntimeEndpoint(endpoint: string, args: string[]): Promise<void> {
@@ -1,5 +1,6 @@
1
1
  import { Command } from "@gajae-code/utils/cli";
2
- import { runBridgedRuntimeEndpoint } from "./gjc-runtime-bridge";
2
+ import { syncSkillActiveState } from "../skill-state/active-state";
3
+ import { runGjcRuntimeBridgeWithHudSidecar } from "./gjc-runtime-bridge";
3
4
 
4
5
  export default class Ralplan extends Command {
5
6
  static description = "Run private GJC RALPLAN workflow commands";
@@ -7,6 +8,24 @@ export default class Ralplan extends Command {
7
8
  static examples = ["$ gjc ralplan --help"];
8
9
 
9
10
  async run(): Promise<void> {
10
- await runBridgedRuntimeEndpoint("ralplan", this.argv);
11
+ const cwd = process.cwd();
12
+ const result = await runGjcRuntimeBridgeWithHudSidecar("ralplan", this.argv, {
13
+ cwd,
14
+ sidecarSkill: "ralplan",
15
+ onHudPayload: payload =>
16
+ syncSkillActiveState({
17
+ cwd,
18
+ skill: "ralplan",
19
+ active: payload.active ?? true,
20
+ phase: payload.phase,
21
+ sessionId: payload.session_id,
22
+ threadId: payload.thread_id,
23
+ turnId: payload.turn_id,
24
+ hud: payload.hud,
25
+ source: "gjc-runtime-bridge",
26
+ }),
27
+ });
28
+ if (result.error) process.stderr.write(`${result.error}\n`);
29
+ process.exitCode = result.status;
11
30
  }
12
31
  }