@gajae-code/coding-agent 0.2.0 → 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 +38 -1
- package/dist/types/cli/skills-cli.d.ts +9 -0
- package/dist/types/commands/contribution-prep.d.ts +18 -0
- package/dist/types/commands/session.d.ts +24 -0
- package/dist/types/commands/skills.d.ts +26 -0
- package/dist/types/config/model-registry.d.ts +33 -4
- package/dist/types/config/models-config-schema.d.ts +52 -5
- 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/model-selector.d.ts +21 -1
- 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/dist/types/slash-commands/builtin-registry.d.ts +1 -0
- package/package.json +7 -7
- package/src/cli/args.ts +17 -2
- package/src/cli/skills-cli.ts +88 -0
- package/src/cli.ts +7 -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/skills.ts +48 -0
- package/src/commands/state.ts +14 -4
- package/src/commands/team.ts +23 -3
- package/src/commit/agentic/index.ts +1 -0
- package/src/commit/pipeline.ts +1 -0
- package/src/config/model-registry.ts +269 -10
- package/src/config/models-config-schema.ts +124 -88
- 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/main.ts +10 -1
- package/src/modes/components/model-selector.ts +109 -28
- 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/controllers/selector-controller.ts +42 -2
- 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 +2 -1
- package/src/session/agent-session.ts +18 -0
- package/src/session/contribution-prep.ts +320 -0
- package/src/setup/provider-onboarding.ts +2 -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/acp-builtins.ts +11 -2
- package/src/slash-commands/builtin-registry.ts +40 -13
- 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
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { CANONICAL_GJC_WORKFLOW_SKILLS, type CanonicalGjcWorkflowSkill, SKILL_ACTIVE_STATE_FILE } from "./active-state";
|
|
3
|
+
|
|
4
|
+
export type { CanonicalGjcWorkflowSkill };
|
|
5
|
+
|
|
6
|
+
export const WORKFLOW_STATE_RECEIPT_VERSION = 1;
|
|
7
|
+
export const WORKFLOW_STATE_RECEIPT_FRESH_MS = 30 * 60 * 1000;
|
|
8
|
+
|
|
9
|
+
export type WorkflowStateMutationOwner = "gjc-state-cli" | "gjc-runtime" | "gjc-hook";
|
|
10
|
+
export type WorkflowStateReceiptStatus = "fresh" | "stale";
|
|
11
|
+
|
|
12
|
+
export interface WorkflowStateReceipt {
|
|
13
|
+
version: 1;
|
|
14
|
+
skill: CanonicalGjcWorkflowSkill;
|
|
15
|
+
owner: WorkflowStateMutationOwner;
|
|
16
|
+
command: string;
|
|
17
|
+
state_path: string;
|
|
18
|
+
storage_path: string;
|
|
19
|
+
mutated_at: string;
|
|
20
|
+
fresh_until: string;
|
|
21
|
+
status: WorkflowStateReceiptStatus;
|
|
22
|
+
mutation_id: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function safeString(value: unknown): string {
|
|
26
|
+
return typeof value === "string" ? value : "";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function encodePathSegment(value: string): string {
|
|
30
|
+
return encodeURIComponent(value).replaceAll(".", "%2E");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function workflowModeStateFileName(skill: CanonicalGjcWorkflowSkill): string {
|
|
34
|
+
return `${skill}-state.json`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function workflowStateStoragePath(cwd: string, skill: CanonicalGjcWorkflowSkill, sessionId?: string): string {
|
|
38
|
+
const normalizedSessionId = safeString(sessionId).trim();
|
|
39
|
+
if (normalizedSessionId) {
|
|
40
|
+
return path.join(
|
|
41
|
+
cwd,
|
|
42
|
+
".gjc",
|
|
43
|
+
"state",
|
|
44
|
+
"sessions",
|
|
45
|
+
encodePathSegment(normalizedSessionId),
|
|
46
|
+
workflowModeStateFileName(skill),
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return path.join(cwd, ".gjc", "state", workflowModeStateFileName(skill));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function workflowActiveStatePath(cwd: string, sessionId?: string): string {
|
|
53
|
+
const normalizedSessionId = safeString(sessionId).trim();
|
|
54
|
+
if (normalizedSessionId) {
|
|
55
|
+
return path.join(
|
|
56
|
+
cwd,
|
|
57
|
+
".gjc",
|
|
58
|
+
"state",
|
|
59
|
+
"sessions",
|
|
60
|
+
encodePathSegment(normalizedSessionId),
|
|
61
|
+
SKILL_ACTIVE_STATE_FILE,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return path.join(cwd, ".gjc", "state", SKILL_ACTIVE_STATE_FILE);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function buildWorkflowStateReceipt(input: {
|
|
68
|
+
cwd: string;
|
|
69
|
+
skill: CanonicalGjcWorkflowSkill;
|
|
70
|
+
owner: WorkflowStateMutationOwner;
|
|
71
|
+
command: string;
|
|
72
|
+
sessionId?: string;
|
|
73
|
+
nowIso?: string;
|
|
74
|
+
mutationId?: string;
|
|
75
|
+
}): WorkflowStateReceipt {
|
|
76
|
+
const mutatedAt = input.nowIso ?? new Date().toISOString();
|
|
77
|
+
const freshUntil = new Date(Date.parse(mutatedAt) + WORKFLOW_STATE_RECEIPT_FRESH_MS).toISOString();
|
|
78
|
+
return {
|
|
79
|
+
version: WORKFLOW_STATE_RECEIPT_VERSION,
|
|
80
|
+
skill: input.skill,
|
|
81
|
+
owner: input.owner,
|
|
82
|
+
command: input.command,
|
|
83
|
+
state_path: workflowActiveStatePath(input.cwd, input.sessionId),
|
|
84
|
+
storage_path: workflowStateStoragePath(input.cwd, input.skill, input.sessionId),
|
|
85
|
+
mutated_at: mutatedAt,
|
|
86
|
+
fresh_until: freshUntil,
|
|
87
|
+
status: "fresh",
|
|
88
|
+
mutation_id: input.mutationId ?? `${input.skill}:${mutatedAt}`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function workflowReceiptStatus(
|
|
93
|
+
receipt: WorkflowStateReceipt | undefined,
|
|
94
|
+
nowMs = Date.now(),
|
|
95
|
+
): WorkflowStateReceiptStatus | undefined {
|
|
96
|
+
if (!receipt) return undefined;
|
|
97
|
+
const freshUntilMs = Date.parse(receipt.fresh_until);
|
|
98
|
+
if (!Number.isFinite(freshUntilMs)) return "stale";
|
|
99
|
+
return nowMs <= freshUntilMs ? "fresh" : "stale";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function canonicalWorkflowSkill(value: string): CanonicalGjcWorkflowSkill | null {
|
|
103
|
+
return (CANONICAL_GJC_WORKFLOW_SKILLS as readonly string[]).includes(value)
|
|
104
|
+
? (value as CanonicalGjcWorkflowSkill)
|
|
105
|
+
: null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function sanctionedWorkflowStateCommand(skill: CanonicalGjcWorkflowSkill): string {
|
|
109
|
+
return `gjc state ${skill} write --input '<json>'`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function describeWorkflowStateContract(skill: CanonicalGjcWorkflowSkill): string[] {
|
|
113
|
+
return [
|
|
114
|
+
`Sanctioned mutation path: gjc state ${skill} read|write --input '<json>'`,
|
|
115
|
+
`Canonical active HUD state: .gjc/state/${SKILL_ACTIVE_STATE_FILE} and .gjc/state/sessions/<session>/${SKILL_ACTIVE_STATE_FILE}`,
|
|
116
|
+
`Skill mode state: .gjc/state/${workflowModeStateFileName(skill)} or .gjc/state/sessions/<session>/${workflowModeStateFileName(skill)}`,
|
|
117
|
+
"Receipts include version, skill, owner, command, state_path, storage_path, mutated_at, fresh_until, status, and mutation_id.",
|
|
118
|
+
"Receipts are fresh for 30 minutes; older receipts are stale and render as HUD warnings.",
|
|
119
|
+
"Planning artifacts under .gjc/specs/** and .gjc/plans/** remain writable outside the state command.",
|
|
120
|
+
];
|
|
121
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { AvailableCommand } from "@agentclientprotocol/sdk";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
BUILTIN_SLASH_COMMANDS_INTERNAL,
|
|
4
|
+
formatUnknownBuiltinSlashCommandDiagnostic,
|
|
5
|
+
lookupBuiltinSlashCommand,
|
|
6
|
+
} from "./builtin-registry";
|
|
3
7
|
import { parseSlashCommand } from "./helpers/parse";
|
|
4
8
|
import type { AcpBuiltinCommandRuntime, AcpBuiltinSlashCommandResult } from "./types";
|
|
5
9
|
|
|
@@ -39,7 +43,12 @@ export async function executeAcpBuiltinSlashCommand(
|
|
|
39
43
|
const parsed = parseSlashCommand(text);
|
|
40
44
|
if (!parsed) return false;
|
|
41
45
|
const command = lookupBuiltinSlashCommand(parsed.name);
|
|
42
|
-
if (!command?.handle)
|
|
46
|
+
if (!command?.handle) {
|
|
47
|
+
const diagnostic = formatUnknownBuiltinSlashCommandDiagnostic(parsed.name);
|
|
48
|
+
if (!diagnostic) return false;
|
|
49
|
+
await runtime.output(diagnostic);
|
|
50
|
+
return { consumed: true };
|
|
51
|
+
}
|
|
43
52
|
const result = await command.handle(parsed, runtime);
|
|
44
53
|
if (result === undefined) return { consumed: true };
|
|
45
54
|
return result;
|
|
@@ -202,17 +202,6 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
202
202
|
runtime.ctx.editor.setText("");
|
|
203
203
|
},
|
|
204
204
|
},
|
|
205
|
-
{
|
|
206
|
-
name: "loop",
|
|
207
|
-
description:
|
|
208
|
-
"Toggle loop mode. While enabled, the next prompt you send re-submits after every yield. Esc cancels the current iteration; /loop again to disable.",
|
|
209
|
-
inlineHint: "[count|duration]",
|
|
210
|
-
allowArgs: true,
|
|
211
|
-
handleTui: async (command, runtime) => {
|
|
212
|
-
await runtime.ctx.handleLoopCommand(command.args);
|
|
213
|
-
runtime.ctx.editor.setText("");
|
|
214
|
-
},
|
|
215
|
-
},
|
|
216
205
|
{
|
|
217
206
|
name: "goal",
|
|
218
207
|
description: "Toggle goal mode (persistent autonomous objective for this session)",
|
|
@@ -222,7 +211,6 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
222
211
|
{ name: "pause", description: "Pause the current goal" },
|
|
223
212
|
{ name: "resume", description: "Resume a paused goal" },
|
|
224
213
|
{ name: "drop", description: "Drop the current goal" },
|
|
225
|
-
{ name: "budget", description: "Adjust the token budget", usage: "<N|off>" },
|
|
226
214
|
],
|
|
227
215
|
inlineHint: "[objective]",
|
|
228
216
|
allowArgs: true,
|
|
@@ -793,6 +781,30 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
793
781
|
await runtime.ctx.handleCompactCommand(customInstructions);
|
|
794
782
|
},
|
|
795
783
|
},
|
|
784
|
+
{
|
|
785
|
+
name: "contribute-pr",
|
|
786
|
+
aliases: ["contribution-prep"],
|
|
787
|
+
description: "Dump redacted session context and spawn a fresh contribute-pr worker",
|
|
788
|
+
inlineHint: "[focus instructions]",
|
|
789
|
+
allowArgs: true,
|
|
790
|
+
handle: async (command, runtime) => {
|
|
791
|
+
const result = await runtime.session.prepareContributionPrep({
|
|
792
|
+
customInstructions: command.args || undefined,
|
|
793
|
+
spawnWorker: true,
|
|
794
|
+
});
|
|
795
|
+
await runtime.output(
|
|
796
|
+
[
|
|
797
|
+
"Contribution prep artifacts written.",
|
|
798
|
+
`Manifest: ${result.manifestPath}`,
|
|
799
|
+
`Worker prompt: ${result.workerPromptPath}`,
|
|
800
|
+
].join("\n"),
|
|
801
|
+
);
|
|
802
|
+
return commandConsumed();
|
|
803
|
+
},
|
|
804
|
+
handleTui: async (command, runtime) => {
|
|
805
|
+
await runtime.ctx.handleContributionPrepCommand(command.args || undefined);
|
|
806
|
+
},
|
|
807
|
+
},
|
|
796
808
|
{
|
|
797
809
|
name: "resume",
|
|
798
810
|
description: "Resume a different session",
|
|
@@ -993,6 +1005,15 @@ for (const command of ACTIVE_BUILTIN_SLASH_COMMAND_REGISTRY) {
|
|
|
993
1005
|
}
|
|
994
1006
|
}
|
|
995
1007
|
|
|
1008
|
+
export function formatUnknownBuiltinSlashCommandDiagnostic(commandName: string): string | undefined {
|
|
1009
|
+
if (commandName !== "provicer") return undefined;
|
|
1010
|
+
return [
|
|
1011
|
+
"Unknown slash command: /provicer.",
|
|
1012
|
+
"Did you mean /provider?",
|
|
1013
|
+
"Run: /provider add --compat <openai|anthropic> --provider <id> --base-url <url> --api-key-env <ENV> --model <model>",
|
|
1014
|
+
].join("\n");
|
|
1015
|
+
}
|
|
1016
|
+
|
|
996
1017
|
/** Builtin command metadata used for slash-command autocomplete and help text. */
|
|
997
1018
|
export const BUILTIN_SLASH_COMMAND_DEFS: ReadonlyArray<BuiltinSlashCommand> = ACTIVE_BUILTIN_SLASH_COMMAND_REGISTRY.map(
|
|
998
1019
|
command => ({
|
|
@@ -1025,7 +1046,13 @@ export async function executeBuiltinSlashCommand(
|
|
|
1025
1046
|
if (!parsed) return false;
|
|
1026
1047
|
|
|
1027
1048
|
const command = BUILTIN_SLASH_COMMAND_LOOKUP.get(parsed.name);
|
|
1028
|
-
if (!command)
|
|
1049
|
+
if (!command) {
|
|
1050
|
+
const diagnostic = formatUnknownBuiltinSlashCommandDiagnostic(parsed.name);
|
|
1051
|
+
if (!diagnostic) return false;
|
|
1052
|
+
runtime.ctx.showError(diagnostic);
|
|
1053
|
+
runtime.ctx.editor.setText("");
|
|
1054
|
+
return true;
|
|
1055
|
+
}
|
|
1029
1056
|
if (parsed.args.length > 0 && !command.allowArgs) {
|
|
1030
1057
|
return false;
|
|
1031
1058
|
}
|
package/src/task/commands.ts
CHANGED
|
@@ -9,12 +9,8 @@ import { type SlashCommand, slashCommandCapability } from "../capability/slash-c
|
|
|
9
9
|
import { loadCapability } from "../discovery";
|
|
10
10
|
// Embed command markdown files at build time
|
|
11
11
|
import initMd from "../prompts/agents/init.md" with { type: "text" };
|
|
12
|
-
import orchestrateMd from "../prompts/commands/orchestrate.md" with { type: "text" };
|
|
13
12
|
|
|
14
|
-
const EMBEDDED_COMMANDS: { name: string; content: string }[] = [
|
|
15
|
-
{ name: "init.md", content: prompt.render(initMd) },
|
|
16
|
-
{ name: "orchestrate.md", content: prompt.render(orchestrateMd) },
|
|
17
|
-
];
|
|
13
|
+
const EMBEDDED_COMMANDS: { name: string; content: string }[] = [{ name: "init.md", content: prompt.render(initMd) }];
|
|
18
14
|
|
|
19
15
|
export const EMBEDDED_COMMAND_TEMPLATES: ReadonlyArray<{ name: string; content: string }> = EMBEDDED_COMMANDS;
|
|
20
16
|
|
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;
|