@gajae-code/coding-agent 0.2.1 → 0.2.2
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 +31 -1
- package/dist/types/commands/contribution-prep.d.ts +18 -0
- package/dist/types/commands/session.d.ts +24 -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 +1 -24
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +15 -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/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/session/agent-session.d.ts +2 -0
- package/dist/types/session/contribution-prep.d.ts +47 -0
- package/dist/types/skill-state/active-state.d.ts +4 -0
- package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +6 -1
- package/dist/types/skill-state/workflow-hud.d.ts +9 -4
- package/dist/types/skill-state/workflow-state-contract.d.ts +34 -0
- package/package.json +7 -7
- package/src/cli/args.ts +3 -2
- package/src/cli.ts +6 -1
- package/src/commands/contribution-prep.ts +41 -0
- package/src/commands/deep-interview.ts +6 -22
- package/src/commands/launch.ts +10 -1
- package/src/commands/ralplan.ts +10 -22
- package/src/commands/session.ts +150 -0
- package/src/commands/state.ts +14 -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 +1 -25
- package/src/config.ts +1 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +14 -13
- package/src/defaults/gjc/skills/ralplan/SKILL.md +14 -2
- package/src/defaults/gjc/skills/team/SKILL.md +29 -7
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +23 -25
- package/src/eval/py/prelude.py +1 -1
- package/src/gjc-runtime/deep-interview-runtime.ts +279 -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 +562 -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 +31 -12
- package/src/internal-urls/docs-index.generated.ts +4 -3
- 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 +25 -1
- package/src/modes/controllers/input-controller.ts +0 -15
- 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/prompts/goals/goal-continuation.md +1 -4
- package/src/prompts/goals/goal-mode-active.md +3 -5
- package/src/prompts/system/system-prompt.md +5 -7
- package/src/prompts/tools/goal.md +4 -4
- package/src/sdk.ts +1 -1
- package/src/session/agent-session.ts +18 -0
- package/src/session/contribution-prep.ts +320 -0
- package/src/skill-state/active-state.ts +38 -0
- package/src/skill-state/deep-interview-mutation-guard.ts +88 -24
- 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 +24 -12
- package/src/task/commands.ts +1 -5
- package/src/tools/gh.ts +212 -2
- package/src/tools/index.ts +2 -5
- 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
package/src/tools/gh.ts
CHANGED
|
@@ -225,6 +225,10 @@ const RUN_SUCCESS_CONCLUSIONS = new Set(["success", "neutral", "skipped"]);
|
|
|
225
225
|
const RUN_FAILURE_CONCLUSIONS = new Set(["failure", "timed_out", "cancelled", "action_required", "startup_failure"]);
|
|
226
226
|
const JOB_FAILURE_CONCLUSIONS = new Set(["failure", "timed_out", "cancelled", "action_required"]);
|
|
227
227
|
|
|
228
|
+
const PR_CREATE_BASE_CONFIG_KEYS = ["github.prBase", "gh.prBase", "gjc.github.prBase"] as const;
|
|
229
|
+
const ISSUE_CLOSING_REFERENCE_PATTERN =
|
|
230
|
+
/(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+(?:https:\/\/github\.com\/([^\s/]+\/[^\s/]+)\/issues\/)?#(\d+)\b/gi;
|
|
231
|
+
|
|
228
232
|
const githubSchema = z
|
|
229
233
|
.object({
|
|
230
234
|
op: z
|
|
@@ -407,6 +411,23 @@ interface GhIssueViewData {
|
|
|
407
411
|
updatedAt?: string;
|
|
408
412
|
url?: string;
|
|
409
413
|
}
|
|
414
|
+
interface GhPrListData {
|
|
415
|
+
baseRefName?: string;
|
|
416
|
+
headRefName?: string;
|
|
417
|
+
isDraft?: boolean;
|
|
418
|
+
number?: number;
|
|
419
|
+
state?: string;
|
|
420
|
+
title?: string;
|
|
421
|
+
url?: string;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
interface PrCreateDuplicateCheck {
|
|
425
|
+
base: string;
|
|
426
|
+
head: string;
|
|
427
|
+
issue?: GhIssueViewData;
|
|
428
|
+
linkedPr?: GhPrListData;
|
|
429
|
+
pr?: GhPrListData;
|
|
430
|
+
}
|
|
410
431
|
|
|
411
432
|
interface GhPrFile {
|
|
412
433
|
path?: string;
|
|
@@ -659,6 +680,185 @@ function resolveSearchLimit(value: number | undefined): number {
|
|
|
659
680
|
|
|
660
681
|
return Math.min(Math.floor(value), SEARCH_LIMIT_MAX);
|
|
661
682
|
}
|
|
683
|
+
async function resolvePrCreateBase(
|
|
684
|
+
cwd: string,
|
|
685
|
+
explicitBase: string | undefined,
|
|
686
|
+
repo: string | undefined,
|
|
687
|
+
signal?: AbortSignal,
|
|
688
|
+
): Promise<string | undefined> {
|
|
689
|
+
if (explicitBase) return explicitBase;
|
|
690
|
+
try {
|
|
691
|
+
for (const key of PR_CREATE_BASE_CONFIG_KEYS) {
|
|
692
|
+
const configured = normalizeOptionalString(await git.config.get(cwd, key, signal));
|
|
693
|
+
if (configured) return configured;
|
|
694
|
+
}
|
|
695
|
+
} catch {
|
|
696
|
+
// Repository config is optional for pr_create; prefer GitHub metadata when
|
|
697
|
+
// local git metadata is unavailable.
|
|
698
|
+
}
|
|
699
|
+
try {
|
|
700
|
+
const resolvedRepo = repo ?? (await resolveDefaultRepoMemoized(cwd, signal));
|
|
701
|
+
const repoView = await git.github.json<GhRepoViewData>(
|
|
702
|
+
cwd,
|
|
703
|
+
["repo", "view", resolvedRepo, "--json", "defaultBranchRef"],
|
|
704
|
+
signal,
|
|
705
|
+
{ repoProvided: true },
|
|
706
|
+
);
|
|
707
|
+
const defaultBranch = normalizeOptionalString(repoView.defaultBranchRef?.name);
|
|
708
|
+
if (defaultBranch) return defaultBranch;
|
|
709
|
+
} catch {
|
|
710
|
+
// Fall back to local git metadata below so pr_create can still work when gh
|
|
711
|
+
// cannot resolve repository metadata in otherwise-valid checkouts.
|
|
712
|
+
}
|
|
713
|
+
return (await git.branch.default(cwd, signal)) ?? undefined;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function normalizePrHead(value: string): string {
|
|
717
|
+
const separator = value.lastIndexOf(":");
|
|
718
|
+
return separator >= 0 ? value.slice(separator + 1) : value;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function extractClosingIssueReferences(body: string | undefined, repo: string | undefined): number[] {
|
|
722
|
+
if (!body) return [];
|
|
723
|
+
const issueNumbers: number[] = [];
|
|
724
|
+
ISSUE_CLOSING_REFERENCE_PATTERN.lastIndex = 0;
|
|
725
|
+
let match = ISSUE_CLOSING_REFERENCE_PATTERN.exec(body);
|
|
726
|
+
while (match !== null) {
|
|
727
|
+
const issueRepo = normalizeOptionalString(match[1]);
|
|
728
|
+
if (!issueRepo || !repo || issueRepo.toLowerCase() === repo.toLowerCase()) {
|
|
729
|
+
const issueNumber = Number(match[2]);
|
|
730
|
+
if (Number.isInteger(issueNumber) && issueNumber > 0) issueNumbers.push(issueNumber);
|
|
731
|
+
}
|
|
732
|
+
match = ISSUE_CLOSING_REFERENCE_PATTERN.exec(body);
|
|
733
|
+
}
|
|
734
|
+
return issueNumbers;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
async function resolvePrCreateHead(
|
|
738
|
+
cwd: string,
|
|
739
|
+
explicitHead: string | undefined,
|
|
740
|
+
signal?: AbortSignal,
|
|
741
|
+
): Promise<string | undefined> {
|
|
742
|
+
if (explicitHead) return explicitHead;
|
|
743
|
+
try {
|
|
744
|
+
return (await git.branch.current(cwd, signal)) ?? undefined;
|
|
745
|
+
} catch {
|
|
746
|
+
return undefined;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
async function fetchPrCreateDuplicateCheck(
|
|
751
|
+
cwd: string,
|
|
752
|
+
repo: string | undefined,
|
|
753
|
+
base: string | undefined,
|
|
754
|
+
head: string | undefined,
|
|
755
|
+
body: string | undefined,
|
|
756
|
+
signal: AbortSignal | undefined,
|
|
757
|
+
): Promise<PrCreateDuplicateCheck | undefined> {
|
|
758
|
+
if (!base || !head) return undefined;
|
|
759
|
+
const resolvedRepo = repo ?? (await resolveDefaultRepoMemoized(cwd, signal));
|
|
760
|
+
const normalizedHead = normalizePrHead(head);
|
|
761
|
+
const prs = await git.github.json<GhPrListData[]>(
|
|
762
|
+
cwd,
|
|
763
|
+
[
|
|
764
|
+
"pr",
|
|
765
|
+
"list",
|
|
766
|
+
"--repo",
|
|
767
|
+
resolvedRepo,
|
|
768
|
+
"--head",
|
|
769
|
+
normalizedHead,
|
|
770
|
+
"--base",
|
|
771
|
+
base,
|
|
772
|
+
"--state",
|
|
773
|
+
"all",
|
|
774
|
+
"--json",
|
|
775
|
+
"number,title,state,url,baseRefName,headRefName,isDraft",
|
|
776
|
+
],
|
|
777
|
+
signal,
|
|
778
|
+
{ repoProvided: true },
|
|
779
|
+
);
|
|
780
|
+
const existingPr = prs.find(pr => pr.headRefName === normalizedHead && pr.baseRefName === base) ?? prs[0];
|
|
781
|
+
const issueNumbers = existingPr ? [] : extractClosingIssueReferences(body, resolvedRepo);
|
|
782
|
+
let issue: GhIssueViewData | undefined;
|
|
783
|
+
let linkedPr: GhPrListData | undefined;
|
|
784
|
+
for (const issueNumber of issueNumbers) {
|
|
785
|
+
const candidateIssue = await git.github.json<GhIssueViewData>(
|
|
786
|
+
cwd,
|
|
787
|
+
[
|
|
788
|
+
"issue",
|
|
789
|
+
"view",
|
|
790
|
+
String(issueNumber),
|
|
791
|
+
"--repo",
|
|
792
|
+
resolvedRepo,
|
|
793
|
+
"--json",
|
|
794
|
+
GH_ISSUE_FIELDS_NO_COMMENTS.join(","),
|
|
795
|
+
],
|
|
796
|
+
signal,
|
|
797
|
+
{ repoProvided: true },
|
|
798
|
+
);
|
|
799
|
+
issue = candidateIssue;
|
|
800
|
+
if (candidateIssue.state && candidateIssue.state.toUpperCase() !== "OPEN") break;
|
|
801
|
+
const linkedPrs = await git.github.json<GhPrListData[]>(
|
|
802
|
+
cwd,
|
|
803
|
+
[
|
|
804
|
+
"pr",
|
|
805
|
+
"list",
|
|
806
|
+
"--repo",
|
|
807
|
+
resolvedRepo,
|
|
808
|
+
"--search",
|
|
809
|
+
`${issueNumber} linked:issue`,
|
|
810
|
+
"--state",
|
|
811
|
+
"open",
|
|
812
|
+
"--json",
|
|
813
|
+
"number,title,state,url,baseRefName,headRefName,isDraft",
|
|
814
|
+
],
|
|
815
|
+
signal,
|
|
816
|
+
{ repoProvided: true },
|
|
817
|
+
);
|
|
818
|
+
linkedPr = linkedPrs.find(pr => pr.headRefName !== normalizedHead) ?? linkedPrs[0];
|
|
819
|
+
if (linkedPr) break;
|
|
820
|
+
}
|
|
821
|
+
return { base, head: normalizedHead, issue, linkedPr, pr: existingPr };
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
function formatPrCreateExistingResult(check: PrCreateDuplicateCheck): string | undefined {
|
|
825
|
+
if (check.issue?.state && check.issue.state.toUpperCase() !== "OPEN") {
|
|
826
|
+
const lines = [
|
|
827
|
+
"# Pull Request Not Created",
|
|
828
|
+
"",
|
|
829
|
+
`Issue #${check.issue.number ?? ""} is ${check.issue.state.toLowerCase()}.`,
|
|
830
|
+
];
|
|
831
|
+
pushLine(lines, "Issue", check.issue.url);
|
|
832
|
+
if (check.pr) pushLine(lines, "Existing PR", check.pr.url);
|
|
833
|
+
pushLine(lines, "Base", check.base);
|
|
834
|
+
pushLine(lines, "Head", check.head);
|
|
835
|
+
return lines.join("\n").trim();
|
|
836
|
+
}
|
|
837
|
+
if (check.linkedPr) {
|
|
838
|
+
const number = check.linkedPr.number !== undefined ? ` #${check.linkedPr.number}` : "";
|
|
839
|
+
const title = check.linkedPr.title ? `: ${check.linkedPr.title}` : "";
|
|
840
|
+
const lines = [`# Pull Request Already Linked${number}${title}`, ""];
|
|
841
|
+
pushLine(lines, "URL", check.linkedPr.url);
|
|
842
|
+
pushLine(lines, "Issue", check.issue?.url);
|
|
843
|
+
pushLine(lines, "State", check.linkedPr.state);
|
|
844
|
+
pushLine(lines, "Draft", check.linkedPr.isDraft);
|
|
845
|
+
pushLine(lines, "Base", check.linkedPr.baseRefName ?? check.base);
|
|
846
|
+
pushLine(lines, "Head", check.linkedPr.headRefName ?? check.head);
|
|
847
|
+
return lines.join("\n").trim();
|
|
848
|
+
}
|
|
849
|
+
if (check.pr) {
|
|
850
|
+
const number = check.pr.number !== undefined ? ` #${check.pr.number}` : "";
|
|
851
|
+
const title = check.pr.title ? `: ${check.pr.title}` : "";
|
|
852
|
+
const lines = [`# Pull Request Already Exists${number}${title}`, ""];
|
|
853
|
+
pushLine(lines, "URL", check.pr.url);
|
|
854
|
+
pushLine(lines, "State", check.pr.state);
|
|
855
|
+
pushLine(lines, "Draft", check.pr.isDraft);
|
|
856
|
+
pushLine(lines, "Base", check.pr.baseRefName ?? check.base);
|
|
857
|
+
pushLine(lines, "Head", check.pr.headRefName ?? check.head);
|
|
858
|
+
return lines.join("\n").trim();
|
|
859
|
+
}
|
|
860
|
+
return undefined;
|
|
861
|
+
}
|
|
662
862
|
|
|
663
863
|
function resolveTailLimit(value: number | undefined): number {
|
|
664
864
|
if (value === undefined) {
|
|
@@ -3117,8 +3317,8 @@ async function executePrCreate(
|
|
|
3117
3317
|
const repo = normalizeOptionalString(params.repo);
|
|
3118
3318
|
const title = normalizeOptionalString(params.title);
|
|
3119
3319
|
const body = params.body;
|
|
3120
|
-
const
|
|
3121
|
-
const
|
|
3320
|
+
const requestedBase = normalizeOptionalString(params.base);
|
|
3321
|
+
const requestedHead = normalizeOptionalString(params.head);
|
|
3122
3322
|
const draft = params.draft ?? false;
|
|
3123
3323
|
const fill = params.fill ?? false;
|
|
3124
3324
|
const reviewers = normalizePrIdentifierList(params.reviewer);
|
|
@@ -3131,6 +3331,16 @@ async function executePrCreate(
|
|
|
3131
3331
|
if (fill && (title || body !== undefined)) {
|
|
3132
3332
|
throw new ToolError("fill is mutually exclusive with title and body");
|
|
3133
3333
|
}
|
|
3334
|
+
const base = await resolvePrCreateBase(session.cwd, requestedBase, repo, signal);
|
|
3335
|
+
const head = await resolvePrCreateHead(session.cwd, requestedHead, signal);
|
|
3336
|
+
const duplicateCheck = await fetchPrCreateDuplicateCheck(session.cwd, repo, base, head, body, signal);
|
|
3337
|
+
const existingText = duplicateCheck ? formatPrCreateExistingResult(duplicateCheck) : undefined;
|
|
3338
|
+
if (existingText) {
|
|
3339
|
+
return buildTextResult(
|
|
3340
|
+
existingText,
|
|
3341
|
+
duplicateCheck?.pr?.url ?? duplicateCheck?.linkedPr?.url ?? duplicateCheck?.issue?.url,
|
|
3342
|
+
);
|
|
3343
|
+
}
|
|
3134
3344
|
|
|
3135
3345
|
const args = ["pr", "create"];
|
|
3136
3346
|
appendRepoFlag(args, repo);
|
package/src/tools/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { EditTool } from "../edit";
|
|
|
7
7
|
import { checkPythonKernelAvailability } from "../eval/py/kernel";
|
|
8
8
|
import type { Skill } from "../extensibility/skills";
|
|
9
9
|
import type { GoalModeState, GoalRuntime } from "../goals";
|
|
10
|
-
import {
|
|
10
|
+
import { GoalTool } from "../goals/tools/goal-tool";
|
|
11
11
|
import type { HindsightSessionState } from "../hindsight/state";
|
|
12
12
|
import { LspTool } from "../lsp";
|
|
13
13
|
import type { PlanModeState } from "../plan-mode/state";
|
|
@@ -309,12 +309,9 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
|
|
|
309
309
|
recall: HindsightRecallTool.createIf,
|
|
310
310
|
reflect: HindsightReflectTool.createIf,
|
|
311
311
|
goal: s => new GoalTool(s),
|
|
312
|
-
get_goal: GetGoalTool.createIf,
|
|
313
|
-
create_goal: CreateGoalTool.createIf,
|
|
314
|
-
update_goal: UpdateGoalTool.createIf,
|
|
315
312
|
};
|
|
316
313
|
|
|
317
|
-
const GOAL_MODE_TOOL_NAMES = [
|
|
314
|
+
const GOAL_MODE_TOOL_NAMES = [] as const;
|
|
318
315
|
|
|
319
316
|
export const HIDDEN_TOOLS: Record<string, ToolFactory> = {
|
|
320
317
|
yield: s => new YieldTool(s),
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { type WorkflowHudSummary } from "../skill-state/active-state";
|
|
2
|
-
export declare const WORKFLOW_HUD_PROTOCOL = "workflow-hud-summary-v1";
|
|
3
|
-
export interface GjcRuntimeBridgeResult {
|
|
4
|
-
status: number;
|
|
5
|
-
error?: string;
|
|
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;
|
|
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>;
|
|
30
|
-
export declare function runBridgedRuntimeEndpoint(endpoint: string, args: string[]): Promise<void>;
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export type LoopLimitConfig = {
|
|
2
|
-
kind: "iterations";
|
|
3
|
-
iterations: number;
|
|
4
|
-
} | {
|
|
5
|
-
kind: "duration";
|
|
6
|
-
durationMs: number;
|
|
7
|
-
};
|
|
8
|
-
export type LoopLimitRuntime = {
|
|
9
|
-
kind: "iterations";
|
|
10
|
-
initial: number;
|
|
11
|
-
remaining: number;
|
|
12
|
-
} | {
|
|
13
|
-
kind: "duration";
|
|
14
|
-
durationMs: number;
|
|
15
|
-
deadlineMs: number;
|
|
16
|
-
};
|
|
17
|
-
export declare function parseLoopLimitArgs(args: string): LoopLimitConfig | undefined | string;
|
|
18
|
-
export declare function createLoopLimitRuntime(config: LoopLimitConfig | undefined, nowMs?: number): LoopLimitRuntime | undefined;
|
|
19
|
-
export declare function consumeLoopLimitIteration(limit: LoopLimitRuntime | undefined, nowMs?: number): boolean;
|
|
20
|
-
export declare function isLoopDurationExpired(limit: LoopLimitRuntime | undefined, nowMs?: number): boolean;
|
|
21
|
-
export declare function describeLoopLimit(config: LoopLimitConfig): string;
|
|
22
|
-
export declare function describeLoopLimitRuntime(limit: LoopLimitRuntime): string;
|
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
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";
|
|
8
|
-
|
|
9
|
-
const BRIDGE_ENV = "GJC_RUNTIME_BINARY";
|
|
10
|
-
const LEGACY_BRIDGE_ENV = "GJC_LEGACY_RUNTIME_BINARY";
|
|
11
|
-
const GUARD_ENV = "GJC_RUNTIME_BRIDGE_ACTIVE";
|
|
12
|
-
export const WORKFLOW_HUD_PROTOCOL = "workflow-hud-summary-v1";
|
|
13
|
-
|
|
14
|
-
export interface GjcRuntimeBridgeResult {
|
|
15
|
-
status: number;
|
|
16
|
-
error?: string;
|
|
17
|
-
}
|
|
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
|
-
|
|
44
|
-
function candidateBinaries(env: NodeJS.ProcessEnv): string[] {
|
|
45
|
-
return [env[BRIDGE_ENV], env[LEGACY_BRIDGE_ENV]].filter(
|
|
46
|
-
(value): value is string => typeof value === "string" && value.trim().length > 0,
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function isPathLike(command: string): boolean {
|
|
51
|
-
return command.includes("/") || command.includes("\\");
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function canAttempt(command: string): boolean {
|
|
55
|
-
return !isPathLike(command) || existsSync(command);
|
|
56
|
-
}
|
|
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
|
-
|
|
113
|
-
export function runGjcRuntimeBridge(
|
|
114
|
-
endpoint: string,
|
|
115
|
-
args: string[],
|
|
116
|
-
env: NodeJS.ProcessEnv = process.env,
|
|
117
|
-
): GjcRuntimeBridgeResult {
|
|
118
|
-
if (env[GUARD_ENV] === "1") {
|
|
119
|
-
return {
|
|
120
|
-
status: 1,
|
|
121
|
-
error: `Refusing recursive gjc runtime bridge for ${endpoint}.`,
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const attempted: string[] = [];
|
|
126
|
-
for (const binary of candidateBinaries(env)) {
|
|
127
|
-
const command = binary.trim();
|
|
128
|
-
if (!canAttempt(command)) continue;
|
|
129
|
-
attempted.push(command);
|
|
130
|
-
const child = spawnSync(command, [endpoint, ...args], {
|
|
131
|
-
stdio: "inherit",
|
|
132
|
-
env: {
|
|
133
|
-
...env,
|
|
134
|
-
[GUARD_ENV]: "1",
|
|
135
|
-
},
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
if (child.error) {
|
|
139
|
-
const error = child.error as NodeJS.ErrnoException;
|
|
140
|
-
if (error.code === "ENOENT") continue;
|
|
141
|
-
return { status: 1, error: error.message };
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return { status: child.status ?? (child.signal ? 1 : 0) };
|
|
145
|
-
}
|
|
146
|
-
|
|
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);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export async function runBridgedRuntimeEndpoint(endpoint: string, args: string[]): Promise<void> {
|
|
224
|
-
const result = runGjcRuntimeBridge(endpoint, args);
|
|
225
|
-
if (result.error) process.stderr.write(`${result.error}\n`);
|
|
226
|
-
process.exitCode = result.status;
|
|
227
|
-
}
|
package/src/commands/question.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { Command } from "@gajae-code/utils/cli";
|
|
2
|
-
import { runBridgedRuntimeEndpoint } from "./gjc-runtime-bridge";
|
|
3
|
-
|
|
4
|
-
export default class Question extends Command {
|
|
5
|
-
static description = "Ask a blocking private runtime question through the GJC bridge";
|
|
6
|
-
static strict = false;
|
|
7
|
-
static examples = ["$ gjc question --input '<json>' --json"];
|
|
8
|
-
|
|
9
|
-
async run(): Promise<void> {
|
|
10
|
-
await runBridgedRuntimeEndpoint("question", this.argv);
|
|
11
|
-
}
|
|
12
|
-
}
|