@gajae-code/coding-agent 0.2.2 → 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 +28 -0
- package/dist/types/cli/setup-cli.d.ts +1 -0
- package/dist/types/commands/deep-interview.d.ts +41 -0
- package/dist/types/commands/setup.d.ts +3 -0
- package/dist/types/config/settings-schema.d.ts +36 -0
- 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 +18 -0
- 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/utils/context-usage.d.ts +6 -2
- package/dist/types/sdk.d.ts +6 -2
- package/dist/types/session/agent-session.d.ts +45 -1
- 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 +26 -1
- package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +1 -1
- package/dist/types/skill-state/initial-phase.d.ts +12 -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/setup-cli.ts +26 -12
- package/src/cli.ts +1 -0
- package/src/commands/deep-interview.ts +25 -2
- package/src/commands/setup.ts +2 -0
- package/src/commands/state.ts +1 -0
- package/src/config/settings-schema.ts +41 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +19 -1
- package/src/defaults/gjc/skills/ralplan/SKILL.md +8 -0
- package/src/defaults/gjc/skills/team/SKILL.md +10 -0
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +10 -0
- package/src/discovery/helpers.ts +24 -1
- package/src/extensibility/extensions/types.ts +6 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +268 -1
- package/src/gjc-runtime/state-runtime.ts +173 -4
- package/src/hooks/skill-state.ts +8 -6
- package/src/internal-urls/docs-index.generated.ts +2 -2
- 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/controllers/command-controller.ts +2 -3
- package/src/modes/controllers/extension-ui-controller.ts +1 -0
- package/src/modes/controllers/selector-controller.ts +4 -11
- 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/system/subagent-system-prompt.md +6 -0
- package/src/prompts/tools/skill.md +28 -0
- package/src/prompts/tools/task.md +3 -0
- package/src/sdk.ts +50 -10
- package/src/session/agent-session.ts +204 -21
- 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 +150 -25
- package/src/skill-state/deep-interview-mutation-guard.ts +11 -24
- package/src/skill-state/initial-phase.ts +17 -0
- package/src/slash-commands/builtin-registry.ts +51 -13
- package/src/slash-commands/helpers/context-report.ts +123 -13
- package/src/task/agents.ts +1 -0
- 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/index.ts +23 -1
- package/src/tools/skill.ts +153 -0
- package/src/utils/changelog.ts +67 -44
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill Tool — agent-initiated skill chaining.
|
|
3
|
+
*
|
|
4
|
+
* Lets the agent hand off to another available skill in the current turn. The
|
|
5
|
+
* callee's SKILL.md is dispatched through the same custom-message path used by
|
|
6
|
+
* `/skill:<name>` typing, as a user-attribution message delivered same-turn
|
|
7
|
+
* (without `deliverAs: "nextTurn"`). Before dispatch, the tool calls
|
|
8
|
+
* `gjc state <caller> handoff --to <callee>` in-process via the state-runtime
|
|
9
|
+
* function so caller and callee mode-states plus `skill-active-state.json`
|
|
10
|
+
* transition atomically.
|
|
11
|
+
*
|
|
12
|
+
* Chaining is refused unless the caller's `current_phase` is in
|
|
13
|
+
* `{complete, completed, handoff, failed, cancelled, canceled, inactive}`. The
|
|
14
|
+
* agent declares readiness either by writing `current_phase: "handoff"` to its
|
|
15
|
+
* mode-state or by running the handoff verb directly.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { AgentTool, AgentToolResult } from "@gajae-code/agent-core";
|
|
19
|
+
import { prompt, untilAborted } from "@gajae-code/utils";
|
|
20
|
+
import * as z from "zod/v4";
|
|
21
|
+
import { buildSkillPromptMessage } from "../extensibility/skills";
|
|
22
|
+
import { runNativeStateCommand } from "../gjc-runtime/state-runtime";
|
|
23
|
+
import skillDescription from "../prompts/tools/skill.md" with { type: "text" };
|
|
24
|
+
import { SKILL_PROMPT_MESSAGE_TYPE } from "../session/messages";
|
|
25
|
+
import type { ToolSession } from ".";
|
|
26
|
+
import { ToolError } from "./tool-errors";
|
|
27
|
+
|
|
28
|
+
const TERMINAL_PHASES = new Set(["complete", "completed", "handoff", "failed", "cancelled", "canceled", "inactive"]);
|
|
29
|
+
|
|
30
|
+
const skillSchema = z.object({
|
|
31
|
+
name: z.string().describe("skill name as it appears in /skill:<name>"),
|
|
32
|
+
args: z.string().describe("argument string passed to the skill").optional(),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
function normalizeSkillName(name: string | undefined): string {
|
|
36
|
+
return (name ?? "").trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type SkillToolInput = z.infer<typeof skillSchema>;
|
|
40
|
+
|
|
41
|
+
export interface SkillToolDetails {
|
|
42
|
+
name: string;
|
|
43
|
+
path: string;
|
|
44
|
+
args?: string;
|
|
45
|
+
lineCount: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class SkillTool implements AgentTool<typeof skillSchema, SkillToolDetails> {
|
|
49
|
+
readonly name = "skill";
|
|
50
|
+
readonly label = "Skill";
|
|
51
|
+
readonly summary = "Chain into another available skill in the current turn";
|
|
52
|
+
readonly loadMode = "discoverable";
|
|
53
|
+
readonly description: string;
|
|
54
|
+
readonly parameters = skillSchema;
|
|
55
|
+
readonly strict = true;
|
|
56
|
+
|
|
57
|
+
readonly #session: ToolSession;
|
|
58
|
+
|
|
59
|
+
constructor(session: ToolSession) {
|
|
60
|
+
this.#session = session;
|
|
61
|
+
this.description = prompt.render(skillDescription);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static createIf(session: ToolSession): SkillTool | null {
|
|
65
|
+
// The tool can only chain when the session can deliver the same-turn
|
|
66
|
+
// custom message. Without `sendCustomMessage` (e.g. minimal tool
|
|
67
|
+
// harnesses in tests) there is nothing useful to do.
|
|
68
|
+
if (!session.sendCustomMessage) return null;
|
|
69
|
+
const skills = session.skills ?? [];
|
|
70
|
+
if (skills.length === 0) return null;
|
|
71
|
+
return new SkillTool(session);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async execute(
|
|
75
|
+
_toolCallId: string,
|
|
76
|
+
input: SkillToolInput,
|
|
77
|
+
signal?: AbortSignal,
|
|
78
|
+
): Promise<AgentToolResult<SkillToolDetails>> {
|
|
79
|
+
return untilAborted(signal, async () => {
|
|
80
|
+
const sendCustomMessage = this.#session.sendCustomMessage;
|
|
81
|
+
if (!sendCustomMessage) {
|
|
82
|
+
throw new ToolError("skill tool: session has no custom-message bridge");
|
|
83
|
+
}
|
|
84
|
+
const skills = this.#session.skills ?? [];
|
|
85
|
+
const requestedName = normalizeSkillName(input.name);
|
|
86
|
+
if (!requestedName) {
|
|
87
|
+
throw new ToolError("skill tool: `name` is required");
|
|
88
|
+
}
|
|
89
|
+
const activeState = this.#session.getActiveSkillState?.();
|
|
90
|
+
const activeSkill = normalizeSkillName(activeState?.skill);
|
|
91
|
+
if (activeSkill && requestedName === activeSkill) {
|
|
92
|
+
throw new ToolError(
|
|
93
|
+
`skill tool: refusing to chain into currently active skill "${requestedName}". Follow the active skill instructions instead of invoking it recursively.`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const skill = skills.find(s => s.name === requestedName);
|
|
98
|
+
if (!skill) {
|
|
99
|
+
const available = skills.map(s => s.name).sort();
|
|
100
|
+
const hint = available.length > 0 ? ` Available: ${available.join(", ")}` : "";
|
|
101
|
+
throw new ToolError(`skill tool: unknown skill "${requestedName}".${hint}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Phase guard + atomic handoff. Only runs when transitioning between
|
|
105
|
+
// distinct skills (same-skill recursion was already refused above).
|
|
106
|
+
if (activeSkill) {
|
|
107
|
+
const phase = (this.#session.getActiveSkillPhase?.() ?? "running").trim().toLowerCase();
|
|
108
|
+
if (!TERMINAL_PHASES.has(phase)) {
|
|
109
|
+
throw new ToolError(
|
|
110
|
+
`skill tool: refusing to chain from "${activeSkill}" (phase=${phase}) into "${requestedName}". Finalize the current skill (gjc state ${activeSkill} write --input '{"current_phase":"handoff"}' --json) or run gjc state ${activeSkill} handoff --to ${requestedName} --json directly before chaining.`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
const cwd = this.#session.cwd;
|
|
114
|
+
const sessionId = activeState?.session_id?.trim();
|
|
115
|
+
const handoffArgs = ["handoff", "--mode", activeSkill, "--to", requestedName, "--json"];
|
|
116
|
+
if (sessionId) {
|
|
117
|
+
handoffArgs.push("--session-id", sessionId);
|
|
118
|
+
}
|
|
119
|
+
const handoff = await runNativeStateCommand(handoffArgs, cwd);
|
|
120
|
+
if (handoff.status !== 0) {
|
|
121
|
+
throw new ToolError(
|
|
122
|
+
`skill tool: handoff failed (status=${handoff.status}): ${(handoff.stderr ?? "").trim() || "no detail"}`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const args = (input.args ?? "").trim();
|
|
128
|
+
const built = await buildSkillPromptMessage(skill, args);
|
|
129
|
+
|
|
130
|
+
await sendCustomMessage(
|
|
131
|
+
{
|
|
132
|
+
customType: SKILL_PROMPT_MESSAGE_TYPE,
|
|
133
|
+
content: built.message,
|
|
134
|
+
display: true,
|
|
135
|
+
details: built.details,
|
|
136
|
+
attribution: "user",
|
|
137
|
+
},
|
|
138
|
+
{ triggerTurn: false },
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const summary = args ? `Handed off to /skill:${skill.name} ${args}.` : `Handed off to /skill:${skill.name}.`;
|
|
142
|
+
return {
|
|
143
|
+
content: [{ type: "text", text: summary }],
|
|
144
|
+
details: {
|
|
145
|
+
name: skill.name,
|
|
146
|
+
path: skill.filePath,
|
|
147
|
+
args: args || undefined,
|
|
148
|
+
lineCount: built.details.lineCount,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
package/src/utils/changelog.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isEnoent, logger } from "@gajae-code/utils";
|
|
2
|
+
import CHANGELOG_TEXT from "../../CHANGELOG.md" with { type: "text" };
|
|
2
3
|
|
|
3
4
|
export interface ChangelogEntry {
|
|
4
5
|
major: number;
|
|
@@ -8,58 +9,67 @@ export interface ChangelogEntry {
|
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
|
-
* Parse changelog entries from CHANGELOG.md
|
|
12
|
-
* Scans for ## lines and collects content until next ## or EOF
|
|
12
|
+
* Parse changelog entries from a CHANGELOG.md text body.
|
|
13
|
+
* Scans for ## lines and collects content until next ## or EOF.
|
|
14
|
+
* Pure and synchronous so it can be reused by the embedded display path.
|
|
13
15
|
*/
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const lines = content.split("\n");
|
|
18
|
-
const entries: ChangelogEntry[] = [];
|
|
16
|
+
export function parseChangelogContent(content: string): ChangelogEntry[] {
|
|
17
|
+
const lines = content.split("\n");
|
|
18
|
+
const entries: ChangelogEntry[] = [];
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
let currentLines: string[] = [];
|
|
21
|
+
let currentVersion: { major: number; minor: number; patch: number } | null = null;
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
for (const line of lines) {
|
|
24
|
+
// Check if this is a version header (## [x.y.z] ...)
|
|
25
|
+
if (line.startsWith("## ")) {
|
|
26
|
+
// Save previous entry if exists
|
|
27
|
+
if (currentVersion && currentLines.length > 0) {
|
|
28
|
+
entries.push({
|
|
29
|
+
...currentVersion,
|
|
30
|
+
content: currentLines.join("\n").trim(),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
} else if (currentVersion) {
|
|
49
|
-
// Collect lines for current version
|
|
50
|
-
currentLines.push(line);
|
|
34
|
+
// Try to parse version from this line
|
|
35
|
+
const versionMatch = line.match(/##\s+\[?(\d+)\.(\d+)\.(\d+)\]?/);
|
|
36
|
+
if (versionMatch) {
|
|
37
|
+
currentVersion = {
|
|
38
|
+
major: Number.parseInt(versionMatch[1], 10),
|
|
39
|
+
minor: Number.parseInt(versionMatch[2], 10),
|
|
40
|
+
patch: Number.parseInt(versionMatch[3], 10),
|
|
41
|
+
};
|
|
42
|
+
currentLines = [line];
|
|
43
|
+
} else {
|
|
44
|
+
// Reset if we can't parse version
|
|
45
|
+
currentVersion = null;
|
|
46
|
+
currentLines = [];
|
|
51
47
|
}
|
|
48
|
+
} else if (currentVersion) {
|
|
49
|
+
// Collect lines for current version
|
|
50
|
+
currentLines.push(line);
|
|
52
51
|
}
|
|
52
|
+
}
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
// Save last entry
|
|
55
|
+
if (currentVersion && currentLines.length > 0) {
|
|
56
|
+
entries.push({
|
|
57
|
+
...currentVersion,
|
|
58
|
+
content: currentLines.join("\n").trim(),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return entries;
|
|
63
|
+
}
|
|
61
64
|
|
|
62
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Parse changelog entries from a CHANGELOG.md file on disk.
|
|
67
|
+
* Returns [] on ENOENT; logs and returns [] on other read/parse errors.
|
|
68
|
+
*/
|
|
69
|
+
export async function parseChangelog(changelogPath: string): Promise<ChangelogEntry[]> {
|
|
70
|
+
try {
|
|
71
|
+
const content = await Bun.file(changelogPath).text();
|
|
72
|
+
return parseChangelogContent(content);
|
|
63
73
|
} catch (error) {
|
|
64
74
|
if (isEnoent(error)) {
|
|
65
75
|
return [];
|
|
@@ -69,6 +79,19 @@ export async function parseChangelog(changelogPath: string): Promise<ChangelogEn
|
|
|
69
79
|
}
|
|
70
80
|
}
|
|
71
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Return changelog entries from the CHANGELOG.md that shipped with this binary.
|
|
84
|
+
*
|
|
85
|
+
* The text is embedded at build time via `with { type: "text" }`, so the
|
|
86
|
+
* displayed changelog is deterministic across compiled binaries, source-tree
|
|
87
|
+
* dev runs, and `GJC_PACKAGE_DIR` / `PI_PACKAGE_DIR` overrides (which scope to
|
|
88
|
+
* optional package assets like docs/examples and do not influence the
|
|
89
|
+
* binary-identity changelog).
|
|
90
|
+
*/
|
|
91
|
+
export function getDisplayChangelogEntries(): ChangelogEntry[] {
|
|
92
|
+
return parseChangelogContent(CHANGELOG_TEXT);
|
|
93
|
+
}
|
|
94
|
+
|
|
72
95
|
/**
|
|
73
96
|
* Compare versions. Returns: -1 if v1 < v2, 0 if v1 === v2, 1 if v1 > v2
|
|
74
97
|
*/
|