@gajae-code/coding-agent 0.4.0 → 0.4.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 +18 -0
- package/dist/types/commands/ultragoal.d.ts +1 -0
- package/dist/types/config/settings-schema.d.ts +11 -3
- package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +29 -0
- package/dist/types/harness-control-plane/finalize.d.ts +8 -0
- package/dist/types/harness-control-plane/receipts.d.ts +16 -1
- package/dist/types/harness-control-plane/types.d.ts +9 -1
- package/dist/types/modes/rpc/rpc-client.d.ts +11 -1
- package/dist/types/reminders/star-reminder.d.ts +115 -0
- package/dist/types/session/agent-session.d.ts +18 -0
- package/examples/extensions/README.md +20 -41
- package/package.json +7 -7
- package/src/cli/grep-cli.ts +1 -1
- package/src/commands/harness.ts +42 -3
- package/src/commands/ultragoal.ts +1 -0
- package/src/config/settings-schema.ts +13 -3
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +19 -23
- package/src/defaults/gjc/skills/ralplan/SKILL.md +7 -7
- package/src/defaults/gjc/skills/team/SKILL.md +10 -1
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +3 -2
- package/src/gjc-runtime/launch-tmux.ts +25 -2
- package/src/gjc-runtime/team-runtime.ts +78 -3
- package/src/gjc-runtime/ultragoal-guard.ts +18 -2
- package/src/gjc-runtime/ultragoal-runtime.ts +240 -30
- package/src/harness-control-plane/finalize.ts +84 -0
- package/src/harness-control-plane/owner.ts +13 -0
- package/src/harness-control-plane/receipts.ts +39 -1
- package/src/harness-control-plane/types.ts +25 -1
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/modes/interactive-mode.ts +26 -0
- package/src/modes/rpc/rpc-client.ts +22 -0
- package/src/prompts/system/system-prompt.md +9 -0
- package/src/reminders/star-reminder.ts +422 -0
- package/src/session/agent-session.ts +79 -13
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.4.2] - 2026-06-09
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Added conservative `timeout-minutes` values to all CI workflow jobs to prevent indefinite hangs.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Improved the grep limit-reached message to show the current limit value and suggest using `--limit` for more results.
|
|
14
|
+
- Fixed a `gjc harness` recovery deadlock where a session created by `start` without `--detach` (persisted as `started` with no owner lease/endpoint) could never get a live owner: `recover` refused to spawn one because no prior endpoint existed, while `start` reported `session-already-exists`. `recover` now bootstraps a fresh owner for a never-started session (no lease, no endpoint, no owner-run evidence) without writing a misleading `vanish` receipt, reported via `bootstrappedOwner: true`. Bootstrap is independent of the vanish classifier's `ownerRequired` verdict (nothing has vanished), so a session started in a non-git workspace (git delta `unknown`) is recovered too, while a deleted worktree is still refused (#421).
|
|
15
|
+
|
|
16
|
+
## [0.4.1] - 2026-06-07
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Hardened the default system prompt with a `<skill-discipline>` block (never ignore skill text, keep read-only/interview skills from mutating, recommend and invoke the matching `/skill` on approval) and tightened `<communication>` to ban permission-begging/deferral phrasing and never announce remaining work instead of doing it (#392).
|
|
21
|
+
- Cleaned up the bundled GJC workflow skill docs and defaulted execution handoff to ultragoal while prioritizing ralplan refinement (#395, #396).
|
|
22
|
+
|
|
5
23
|
## [0.4.0] - 2026-06-06
|
|
6
24
|
|
|
7
25
|
### Added
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { type SkillDiscoverySettings } from "./skill-settings-defaults";
|
|
2
2
|
/** Unified settings schema - single source of truth for all settings.
|
|
3
|
-
* Unified settings schema - single source of truth for all settings.
|
|
4
3
|
*
|
|
5
4
|
* Each setting is defined once here with:
|
|
6
5
|
* - Type and default value
|
|
@@ -1144,6 +1143,15 @@ export declare const SETTINGS_SCHEMA: {
|
|
|
1144
1143
|
readonly description: "If false, skip update check";
|
|
1145
1144
|
};
|
|
1146
1145
|
};
|
|
1146
|
+
readonly "starReminder.enabled": {
|
|
1147
|
+
readonly type: "boolean";
|
|
1148
|
+
readonly default: true;
|
|
1149
|
+
readonly ui: {
|
|
1150
|
+
readonly tab: "interaction";
|
|
1151
|
+
readonly label: "GitHub Star Reminder";
|
|
1152
|
+
readonly description: "Show the interactive GitHub star reminder when gh is authenticated";
|
|
1153
|
+
};
|
|
1154
|
+
};
|
|
1147
1155
|
readonly collapseChangelog: {
|
|
1148
1156
|
readonly type: "boolean";
|
|
1149
1157
|
readonly default: false;
|
|
@@ -1260,11 +1268,11 @@ export declare const SETTINGS_SCHEMA: {
|
|
|
1260
1268
|
};
|
|
1261
1269
|
readonly "contextPromotion.enabled": {
|
|
1262
1270
|
readonly type: "boolean";
|
|
1263
|
-
readonly default:
|
|
1271
|
+
readonly default: false;
|
|
1264
1272
|
readonly ui: {
|
|
1265
1273
|
readonly tab: "context";
|
|
1266
1274
|
readonly label: "Auto-Promote Context";
|
|
1267
|
-
readonly description: "Promote to a larger-context model on context overflow instead of compacting";
|
|
1275
|
+
readonly description: "Promote to a larger-context model on context overflow instead of compacting (off by default; opt in to enable)";
|
|
1268
1276
|
};
|
|
1269
1277
|
};
|
|
1270
1278
|
readonly "compaction.enabled": {
|
|
@@ -22,6 +22,7 @@ export interface TmuxLaunchContext {
|
|
|
22
22
|
currentBranch?: string | null;
|
|
23
23
|
existingBranchSessionName?: string | null;
|
|
24
24
|
project?: string | null;
|
|
25
|
+
diagnosticWriter?: (message: string) => void;
|
|
25
26
|
}
|
|
26
27
|
export interface TmuxSpawnResult {
|
|
27
28
|
exitCode: number | null;
|
|
@@ -99,6 +99,17 @@ export declare function createUltragoalPlan(input: {
|
|
|
99
99
|
brief: string;
|
|
100
100
|
gjcGoalMode?: UltragoalGjcGoalMode;
|
|
101
101
|
}): Promise<UltragoalPlan>;
|
|
102
|
+
export interface UltragoalRunCompletionState {
|
|
103
|
+
requiredGoals: UltragoalGoal[];
|
|
104
|
+
incompleteGoals: UltragoalGoal[];
|
|
105
|
+
nextGoal?: UltragoalGoal;
|
|
106
|
+
allComplete: boolean;
|
|
107
|
+
hasBlockers: boolean;
|
|
108
|
+
needsFinalAggregateReceipt: boolean;
|
|
109
|
+
}
|
|
110
|
+
export declare function getUltragoalRunCompletionState(plan: UltragoalPlan, options?: {
|
|
111
|
+
retryFailed?: boolean;
|
|
112
|
+
}): UltragoalRunCompletionState;
|
|
102
113
|
export declare function startNextUltragoalGoal(input: {
|
|
103
114
|
cwd: string;
|
|
104
115
|
retryFailed?: boolean;
|
|
@@ -115,6 +126,24 @@ export declare function checkpointUltragoalGoal(input: {
|
|
|
115
126
|
gjcGoalJson?: string;
|
|
116
127
|
qualityGateJson?: string;
|
|
117
128
|
}): Promise<UltragoalPlan>;
|
|
129
|
+
export interface UltragoalCheckpointContinuation {
|
|
130
|
+
plan: UltragoalPlan;
|
|
131
|
+
checkpointedGoal: UltragoalGoal;
|
|
132
|
+
nextGoal?: UltragoalGoal;
|
|
133
|
+
startedNext: boolean;
|
|
134
|
+
allComplete: boolean;
|
|
135
|
+
incompleteGoals: UltragoalGoal[];
|
|
136
|
+
}
|
|
137
|
+
export declare function checkpointAndContinueUltragoalGoal(input: {
|
|
138
|
+
cwd: string;
|
|
139
|
+
goalId: string;
|
|
140
|
+
status: UltragoalGoalStatus;
|
|
141
|
+
evidence: string;
|
|
142
|
+
gjcGoalJson?: string;
|
|
143
|
+
qualityGateJson?: string;
|
|
144
|
+
advanceNext?: boolean;
|
|
145
|
+
retryFailed?: boolean;
|
|
146
|
+
}): Promise<UltragoalCheckpointContinuation>;
|
|
118
147
|
export declare function addUltragoalSubgoal(input: {
|
|
119
148
|
cwd: string;
|
|
120
149
|
title: string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ReviewVerdict } from "./types";
|
|
1
2
|
export interface ValidationCommandSpec {
|
|
2
3
|
name: string;
|
|
3
4
|
command: string;
|
|
@@ -25,6 +26,12 @@ export interface FinalizeOptions {
|
|
|
25
26
|
requireTests?: boolean;
|
|
26
27
|
requireCommit?: boolean;
|
|
27
28
|
requirePr?: boolean;
|
|
29
|
+
/** Review-only sessions produce a terminal verdict instead of implementation validation. */
|
|
30
|
+
reviewOnly?: boolean;
|
|
31
|
+
/** Operator/loop-supplied terminal review verdict (closed vocabulary). */
|
|
32
|
+
verdict?: string | null;
|
|
33
|
+
/** Bounded PR/issue reference for the review target (e.g. "PR-414"). Never resolved from the live repo. */
|
|
34
|
+
prTarget?: string | null;
|
|
28
35
|
validationCommands?: ValidationCommandSpec[];
|
|
29
36
|
checks: FinalizeChecks;
|
|
30
37
|
clock?: () => number;
|
|
@@ -39,6 +46,7 @@ export interface FinalizeResult {
|
|
|
39
46
|
}[];
|
|
40
47
|
commitHash: string | null;
|
|
41
48
|
prUrl: string | null;
|
|
49
|
+
verdict?: ReviewVerdict | null;
|
|
42
50
|
issueArtifact: string | null;
|
|
43
51
|
blockers: string[];
|
|
44
52
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type GitDelta, type ReceiptFamily, type RecoveryClassification, type ReviewVerdict } from "./types";
|
|
2
2
|
export interface ReceiptSubject {
|
|
3
3
|
workspace: string;
|
|
4
4
|
branch: string | null;
|
|
@@ -84,5 +84,20 @@ export interface CompletionEvidence {
|
|
|
84
84
|
finalizedAt: string;
|
|
85
85
|
blockers: string[];
|
|
86
86
|
}
|
|
87
|
+
export interface ReviewVerdictEvidence {
|
|
88
|
+
verdict: ReviewVerdict;
|
|
89
|
+
prTarget: string | null;
|
|
90
|
+
finalizedAt: string;
|
|
91
|
+
/** Bounded summary code/reference for the verdict; never raw assistant text. */
|
|
92
|
+
summaryRef: string | null;
|
|
93
|
+
}
|
|
94
|
+
export interface ReviewFailureEvidence {
|
|
95
|
+
/** Machine-actionable reason the review produced no terminal verdict. */
|
|
96
|
+
reason: string;
|
|
97
|
+
prTarget: string | null;
|
|
98
|
+
failedAt: string;
|
|
99
|
+
/** Routing hint for the operator/fallback path. */
|
|
100
|
+
fallback: string;
|
|
101
|
+
}
|
|
87
102
|
/** Classifications that MUST have a valid `vanish` receipt before the action proceeds. */
|
|
88
103
|
export declare function requiresVanishBeforeAction(classification: RecoveryClassification): boolean;
|
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
*/
|
|
10
10
|
/** Harnesses the control plane can operate. v1 implements `gajae-code` only. */
|
|
11
11
|
export type Harness = "gajae-code" | "codex" | "omx";
|
|
12
|
+
/** Operating mode of a session. `implement` builds/changes code; `review` produces a read-only verdict. */
|
|
13
|
+
export type SessionMode = "implement" | "review";
|
|
14
|
+
/** Closed vocabulary of terminal review verdicts a review-only session may emit. */
|
|
15
|
+
export type ReviewVerdict = "APPROVE_MERGE_READY" | "REQUEST_CHANGES" | "OWNER_CONFIRMATION_REQUIRED";
|
|
16
|
+
export declare const REVIEW_VERDICTS: readonly ReviewVerdict[];
|
|
17
|
+
export declare function isReviewVerdict(value: unknown): value is ReviewVerdict;
|
|
12
18
|
/** Lifecycle states of an operated session. */
|
|
13
19
|
export type HarnessLifecycle = "new" | "started" | "submitted" | "observing" | "recovering" | "validating" | "finalizing" | "completed" | "blocked" | "retired";
|
|
14
20
|
/** Event severities emitted by the owner. */
|
|
@@ -20,7 +26,7 @@ export type RiskKind = "normal" | "prompt-not-accepted" | "deleted-worktree" | "
|
|
|
20
26
|
/** Deterministic recovery classifications. */
|
|
21
27
|
export type RecoveryClassification = "continue" | "send-enter" | "reinject-prompt" | "restart-clean" | "restart-preserve-delta" | "fallback-codex-exec" | "human-check";
|
|
22
28
|
/** Receipt families persisted under the session storage dir. */
|
|
23
|
-
export type ReceiptFamily = "vanish" | "prompt-acceptance" | "validation" | "completion";
|
|
29
|
+
export type ReceiptFamily = "vanish" | "prompt-acceptance" | "validation" | "completion" | "review-verdict" | "review-failure";
|
|
24
30
|
/** The CLI verbs / primitives exposed by `gjc harness <verb>`. */
|
|
25
31
|
export type HarnessVerb = "start" | "submit" | "observe" | "classify" | "recover" | "validate" | "finalize" | "retire" | "events" | "monitor" | "operate";
|
|
26
32
|
/** Submission transports. */
|
|
@@ -54,6 +60,8 @@ export interface PrimitiveResponse<E = Record<string, unknown>> {
|
|
|
54
60
|
export interface SessionHandle {
|
|
55
61
|
sessionId: string;
|
|
56
62
|
harness: Harness;
|
|
63
|
+
/** Operating mode; absent on legacy records means `implement`. */
|
|
64
|
+
mode?: SessionMode;
|
|
57
65
|
repo: string | null;
|
|
58
66
|
workspace: string;
|
|
59
67
|
branch: string | null;
|
|
@@ -8,7 +8,7 @@ import type { CompactionResult } from "@gajae-code/agent-core/compaction";
|
|
|
8
8
|
import type { ImageContent, Model } from "@gajae-code/ai";
|
|
9
9
|
import type { BashResult } from "../../exec/bash-executor";
|
|
10
10
|
import type { SessionStats } from "../../session/agent-session";
|
|
11
|
-
import type { RpcHandoffResult, RpcHostToolDefinition, RpcSessionState, RpcWorkflowGate, RpcWorkflowGateResolution } from "./rpc-types";
|
|
11
|
+
import type { RpcExtensionUIRequest, RpcHandoffResult, RpcHostToolDefinition, RpcSessionState, RpcUnattendedAccepted, RpcUnattendedDeclaration, RpcWorkflowGate, RpcWorkflowGateResolution } from "./rpc-types";
|
|
12
12
|
export interface RpcClientOptions {
|
|
13
13
|
/** Path to the CLI entry point (default: searches for dist/cli.js) */
|
|
14
14
|
cliPath?: string;
|
|
@@ -68,6 +68,16 @@ export declare class RpcClient {
|
|
|
68
68
|
* Answer a workflow lifecycle gate and wait for the server resolution envelope.
|
|
69
69
|
*/
|
|
70
70
|
respondGate(gateId: string, answer: unknown, idempotencyKey?: string): Promise<RpcWorkflowGateResolution>;
|
|
71
|
+
/**
|
|
72
|
+
* Subscribe to extension UI requests emitted by the server (e.g. select /
|
|
73
|
+
* input / editor / confirm). Returns an unsubscribe function.
|
|
74
|
+
*/
|
|
75
|
+
onExtensionUiRequest(listener: (req: RpcExtensionUIRequest) => void): () => void;
|
|
76
|
+
/**
|
|
77
|
+
* Enter unattended mode by declaring budget + scopes + action allowlist.
|
|
78
|
+
* Returns the accepted declaration, or rejects (fail-closed) on refusal.
|
|
79
|
+
*/
|
|
80
|
+
negotiateUnattended(declaration: RpcUnattendedDeclaration): Promise<RpcUnattendedAccepted>;
|
|
71
81
|
/**
|
|
72
82
|
* Get collected stderr output (useful for debugging).
|
|
73
83
|
*/
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { ImageContent } from "@gajae-code/ai";
|
|
2
|
+
import type { CustomMessage } from "../session/messages";
|
|
3
|
+
export declare const STAR_REMINDER_REPO = "Yeachan-Heo/gajae-code";
|
|
4
|
+
export declare const STAR_REMINDER_CUSTOM_TYPE = "star-reminder";
|
|
5
|
+
export declare const STARRED_CACHE_TTL_MS: number;
|
|
6
|
+
export interface StarReminderState {
|
|
7
|
+
declined: boolean;
|
|
8
|
+
starred: boolean;
|
|
9
|
+
/** ISO-8601 timestamp of the last authoritative star check, or "" if never. */
|
|
10
|
+
starredCheckedAt: string;
|
|
11
|
+
}
|
|
12
|
+
export type StarCheckStatus = "starred" | "unstarred" | "unavailable";
|
|
13
|
+
export interface GhResult {
|
|
14
|
+
exitCode: number;
|
|
15
|
+
stdout: string;
|
|
16
|
+
stderr: string;
|
|
17
|
+
timedOut?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export type RunGh = (args: string[], options?: {
|
|
20
|
+
timeoutMs?: number;
|
|
21
|
+
}) => Promise<GhResult>;
|
|
22
|
+
export interface StarReminderDeps {
|
|
23
|
+
statePath?: string;
|
|
24
|
+
now?: () => Date;
|
|
25
|
+
runGh?: RunGh;
|
|
26
|
+
sleep?: (ms: number) => Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
export declare function getStarReminderStatePath(): string;
|
|
29
|
+
export declare function defaultStarReminderState(): StarReminderState;
|
|
30
|
+
/** Read state without locking. Missing or malformed files return the default. */
|
|
31
|
+
export declare function readStarReminderStateUnlocked(statePath?: string): Promise<StarReminderState>;
|
|
32
|
+
/** Whether a stored `starred:true` is still within the 24h cache window. */
|
|
33
|
+
export declare function isStarredCacheFresh(state: StarReminderState, now?: Date): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Lock-protected read-modify-write. The mutator receives the freshly re-read
|
|
36
|
+
* state under the lock; callers MUST base monotonic decisions on that value,
|
|
37
|
+
* not on any snapshot captured before the lock was held.
|
|
38
|
+
*/
|
|
39
|
+
export declare function updateStarReminderStateLocked(mutator: (current: StarReminderState) => StarReminderState | Promise<StarReminderState>, deps?: StarReminderDeps): Promise<StarReminderState>;
|
|
40
|
+
/** A successful PUT is authoritative: always record starred and clear declined. */
|
|
41
|
+
export declare function recordStarredFromPut(deps?: StarReminderDeps): Promise<StarReminderState>;
|
|
42
|
+
/**
|
|
43
|
+
* Record the result of a fresh `gh` star check performed by this operation.
|
|
44
|
+
* - "starred": authoritative, clears declined so all reminders stop.
|
|
45
|
+
* - "unstarred": may downgrade, but only when the current state is not a
|
|
46
|
+
* still-fresh confirmed star (which a concurrent process may have just
|
|
47
|
+
* written); in that case the fresher confirmation wins.
|
|
48
|
+
*/
|
|
49
|
+
export declare function recordFreshStarCheck(status: "starred" | "unstarred", deps?: StarReminderDeps): Promise<StarReminderState>;
|
|
50
|
+
/** Record a launch-nudge decline. Never downgrades a confirmed star. */
|
|
51
|
+
export declare function recordDeclinedAfterNo(deps?: StarReminderDeps): Promise<StarReminderState>;
|
|
52
|
+
/** Default `gh` runner. Returns an unavailable-style result instead of throwing. */
|
|
53
|
+
export declare function runGhDefault(args: string[], options?: {
|
|
54
|
+
timeoutMs?: number;
|
|
55
|
+
}): Promise<GhResult>;
|
|
56
|
+
/** Classify the star state of the repo via `gh api`. */
|
|
57
|
+
export declare function checkGhStarred(deps?: StarReminderDeps): Promise<StarCheckStatus>;
|
|
58
|
+
/** Star the repo via `gh api -X PUT`. Returns whether the PUT succeeded. */
|
|
59
|
+
export declare function autoStarRepo(deps?: StarReminderDeps): Promise<boolean>;
|
|
60
|
+
/**
|
|
61
|
+
* Determine the star state for this session, hitting `gh` only when needed.
|
|
62
|
+
* A fresh cached star skips `gh`; unstarred and declined states are rechecked.
|
|
63
|
+
* Authoritative results are persisted via the monotonic helpers.
|
|
64
|
+
*/
|
|
65
|
+
export declare function refreshStarStateForSession(deps?: StarReminderDeps): Promise<StarCheckStatus>;
|
|
66
|
+
export interface StarReminderPromptUI {
|
|
67
|
+
/** Show a yes/no confirmation. Resolves true when the user accepts. */
|
|
68
|
+
confirm(title: string, message: string): Promise<boolean>;
|
|
69
|
+
/** Optional guard; when it returns false the nudge is skipped silently. */
|
|
70
|
+
isIdle?: () => boolean;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Run the launch nudge once. Caller is responsible for the `startup.quiet`,
|
|
74
|
+
* `starReminder.enabled`, and true-interactive gates. All errors are swallowed
|
|
75
|
+
* so the launch path can never be broken by the reminder.
|
|
76
|
+
*/
|
|
77
|
+
export declare function maybeShowLaunchStarReminder(ui: StarReminderPromptUI, deps?: StarReminderDeps): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Schedule the launch nudge to run after the first render so the networked
|
|
80
|
+
* `gh` check never blocks startup. Returns immediately.
|
|
81
|
+
*/
|
|
82
|
+
export declare function scheduleLaunchStarReminderAfterFirstRender(ui: StarReminderPromptUI, deps?: StarReminderDeps): void;
|
|
83
|
+
export interface StarReminderLaunchGateInput {
|
|
84
|
+
/** The `starReminder.enabled` setting. */
|
|
85
|
+
enabled: boolean;
|
|
86
|
+
/** The `startup.quiet` setting. */
|
|
87
|
+
quiet: boolean;
|
|
88
|
+
}
|
|
89
|
+
export interface StarReminderLaunchGate {
|
|
90
|
+
/** Whether to register the decline-driven injection contributor. */
|
|
91
|
+
register: boolean;
|
|
92
|
+
/** Whether to schedule the launch nudge after first render. */
|
|
93
|
+
schedule: boolean;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Pure decision for interactive wiring. The injection contributor is registered
|
|
97
|
+
* whenever the feature is enabled; the launch nudge is additionally suppressed
|
|
98
|
+
* by quiet startup. Centralizing this keeps the interactive gate testable.
|
|
99
|
+
*/
|
|
100
|
+
export declare function starReminderLaunchGate(input: StarReminderLaunchGateInput): StarReminderLaunchGate;
|
|
101
|
+
export interface StarReminderSessionRef {
|
|
102
|
+
getSessionId(): string | undefined;
|
|
103
|
+
}
|
|
104
|
+
export type StarReminderCustomMessage = Pick<CustomMessage, "customType" | "content" | "display" | "details" | "attribution">;
|
|
105
|
+
export type InternalBeforeAgentStartContributor = (event: {
|
|
106
|
+
prompt: string;
|
|
107
|
+
images?: ImageContent[];
|
|
108
|
+
sessionId: string | undefined;
|
|
109
|
+
}) => Promise<StarReminderCustomMessage | undefined>;
|
|
110
|
+
export declare function createStarReminderMessage(): StarReminderCustomMessage;
|
|
111
|
+
/**
|
|
112
|
+
* Build a before-agent-start contributor that injects the persuasion message
|
|
113
|
+
* once per logical session id, for declined-and-still-unstarred users only.
|
|
114
|
+
*/
|
|
115
|
+
export declare function createStarReminderBeforeAgentStartContributor(session: StarReminderSessionRef, deps?: StarReminderDeps): InternalBeforeAgentStartContributor;
|
|
@@ -294,6 +294,18 @@ export interface SessionStats {
|
|
|
294
294
|
premiumRequests: number;
|
|
295
295
|
cost: number;
|
|
296
296
|
}
|
|
297
|
+
/** A custom message contributed at the before-agent-start point. */
|
|
298
|
+
export type BeforeAgentStartInternalMessage = Pick<CustomMessage, "customType" | "content" | "display" | "details" | "attribution">;
|
|
299
|
+
/**
|
|
300
|
+
* Internal (first-party, non-user-hook) contributor invoked at the active
|
|
301
|
+
* before-agent-start point alongside the extension runner. Returns an optional
|
|
302
|
+
* custom message to append to the prompt context. Errors are nonfatal.
|
|
303
|
+
*/
|
|
304
|
+
export type BeforeAgentStartContributor = (event: {
|
|
305
|
+
prompt: string;
|
|
306
|
+
images?: ImageContent[];
|
|
307
|
+
sessionId: string | undefined;
|
|
308
|
+
}) => Promise<BeforeAgentStartInternalMessage | undefined>;
|
|
297
309
|
export declare class AgentSession {
|
|
298
310
|
#private;
|
|
299
311
|
readonly agent: Agent;
|
|
@@ -991,6 +1003,12 @@ export declare class AgentSession {
|
|
|
991
1003
|
* Check if extensions have handlers for a specific event type.
|
|
992
1004
|
*/
|
|
993
1005
|
hasExtensionHandlers(eventType: string): boolean;
|
|
1006
|
+
/**
|
|
1007
|
+
* Register a first-party internal before-agent-start contributor. Returns an
|
|
1008
|
+
* unregister function. This is NOT user-facing hook discovery; it is an
|
|
1009
|
+
* in-core seam invoked alongside the extension runner.
|
|
1010
|
+
*/
|
|
1011
|
+
registerBeforeAgentStartContributor(contributor: BeforeAgentStartContributor): () => void;
|
|
994
1012
|
/**
|
|
995
1013
|
* Get the extension runner (for setting UI context and error handlers).
|
|
996
1014
|
*/
|
|
@@ -5,57 +5,37 @@ Example extensions for gajae-code.
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
#
|
|
9
|
-
|
|
8
|
+
# Copy an existing extension into the user extension directory for auto-discovery
|
|
9
|
+
mkdir -p ~/.gjc/agent/extensions
|
|
10
|
+
cp packages/coding-agent/examples/extensions/hello.ts ~/.gjc/agent/extensions/
|
|
10
11
|
|
|
11
|
-
#
|
|
12
|
-
|
|
12
|
+
# Project-local extensions can live in .gjc/extensions/
|
|
13
|
+
mkdir -p .gjc/extensions
|
|
14
|
+
cp packages/coding-agent/examples/extensions/pirate.ts .gjc/extensions/
|
|
13
15
|
```
|
|
14
16
|
|
|
15
17
|
## Examples
|
|
16
18
|
|
|
17
|
-
###
|
|
19
|
+
### Custom Tools & API
|
|
18
20
|
|
|
19
|
-
| Extension
|
|
20
|
-
|
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
23
|
-
| `confirm-destructive.ts` | Confirms before destructive session actions (clear, switch, branch) |
|
|
24
|
-
| `dirty-repo-guard.ts` | Prevents session changes with uncommitted git changes |
|
|
25
|
-
|
|
26
|
-
### Custom Tools
|
|
27
|
-
|
|
28
|
-
| Extension | Description |
|
|
29
|
-
| ------------- | ----------------------------------------------------------------------------- |
|
|
30
|
-
| `todo.ts` | Todo list tool + `/todos` command with custom rendering and state persistence |
|
|
31
|
-
| `hello.ts` | Minimal custom tool example |
|
|
32
|
-
| `question.ts` | Demonstrates `ctx.ui.select()` for asking the user questions |
|
|
33
|
-
| `subagent/` | Delegate tasks to specialized subagents with isolated context windows |
|
|
21
|
+
| Extension | Description |
|
|
22
|
+
| ------------- | ---------------------------------------------------------- |
|
|
23
|
+
| `hello.ts` | Minimal custom tool example |
|
|
24
|
+
| `api-demo.ts` | Demonstrates logger access, injected `pi.zod`, and modules |
|
|
34
25
|
|
|
35
26
|
### Commands & UI
|
|
36
27
|
|
|
37
|
-
| Extension
|
|
38
|
-
|
|
|
39
|
-
| `plan-mode.ts`
|
|
40
|
-
| `tools.ts`
|
|
41
|
-
| `
|
|
42
|
-
| `qna.ts` | Extracts questions from last response into editor via `ctx.ui.setEditorText()` |
|
|
43
|
-
| `status-line.ts` | Shows turn progress in footer via `ctx.ui.setStatus()` with themed colors |
|
|
44
|
-
| `snake.ts` | Snake game with custom UI, keyboard handling, and session persistence |
|
|
45
|
-
|
|
46
|
-
### Git Integration
|
|
47
|
-
|
|
48
|
-
| Extension | Description |
|
|
49
|
-
| ------------------------ | ------------------------------------------------------------------------- |
|
|
50
|
-
| `git-checkpoint.ts` | Creates git stash checkpoints at each turn for code restoration on branch |
|
|
51
|
-
| `auto-commit-on-exit.ts` | Auto-commits on exit using last assistant message for commit message |
|
|
28
|
+
| Extension | Description |
|
|
29
|
+
| ------------------- | ----------------------------------------------------------------------------- |
|
|
30
|
+
| `plan-mode.ts` | Anthropic Code-style plan mode for read-only exploration with `/plan` command |
|
|
31
|
+
| `tools.ts` | Interactive `/tools` command to enable/disable tools with session persistence |
|
|
32
|
+
| `reload-runtime.ts` | Adds a command and tool for reloading extensions, skills, prompts, and themes |
|
|
52
33
|
|
|
53
34
|
### System Prompt & Compaction
|
|
54
35
|
|
|
55
|
-
| Extension
|
|
56
|
-
|
|
|
57
|
-
| `pirate.ts`
|
|
58
|
-
| `custom-compaction.ts` | Custom compaction that summarizes entire conversation |
|
|
36
|
+
| Extension | Description |
|
|
37
|
+
| ----------- | -------------------------------------------------------------------- |
|
|
38
|
+
| `pirate.ts` | Demonstrates `systemPromptAppend` to dynamically modify system prompt |
|
|
59
39
|
|
|
60
40
|
### External Dependencies
|
|
61
41
|
|
|
@@ -63,11 +43,10 @@ cp permission-gate.ts ~/.gjc/agent/extensions/
|
|
|
63
43
|
| ----------------- | ------------------------------------------------------------------------- |
|
|
64
44
|
| `chalk-logger.ts` | Uses chalk from parent node_modules (demonstrates jiti module resolution) |
|
|
65
45
|
| `with-deps/` | Extension with its own package.json and dependencies |
|
|
66
|
-
| `file-trigger.ts` | Watches a trigger file and injects contents into conversation |
|
|
67
46
|
|
|
68
47
|
## Writing Extensions
|
|
69
48
|
|
|
70
|
-
|
|
49
|
+
The examples below show the core extension patterns used by this directory.
|
|
71
50
|
|
|
72
51
|
```typescript
|
|
73
52
|
import type { ExtensionAPI } from "@gajae-code/coding-agent";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/coding-agent",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.2",
|
|
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",
|
|
@@ -50,12 +50,12 @@
|
|
|
50
50
|
"@agentclientprotocol/sdk": "0.21.0",
|
|
51
51
|
"@babel/parser": "^7.29.3",
|
|
52
52
|
"@mozilla/readability": "^0.6.0",
|
|
53
|
-
"@gajae-code/stats": "0.4.
|
|
54
|
-
"@gajae-code/agent-core": "0.4.
|
|
55
|
-
"@gajae-code/ai": "0.4.
|
|
56
|
-
"@gajae-code/natives": "0.4.
|
|
57
|
-
"@gajae-code/tui": "0.4.
|
|
58
|
-
"@gajae-code/utils": "0.4.
|
|
53
|
+
"@gajae-code/stats": "0.4.2",
|
|
54
|
+
"@gajae-code/agent-core": "0.4.2",
|
|
55
|
+
"@gajae-code/ai": "0.4.2",
|
|
56
|
+
"@gajae-code/natives": "0.4.2",
|
|
57
|
+
"@gajae-code/tui": "0.4.2",
|
|
58
|
+
"@gajae-code/utils": "0.4.2",
|
|
59
59
|
"@puppeteer/browsers": "^2.13.0",
|
|
60
60
|
"@types/turndown": "5.0.6",
|
|
61
61
|
"@xterm/headless": "^6.0.0",
|
package/src/cli/grep-cli.ts
CHANGED
|
@@ -98,7 +98,7 @@ export async function runGrepCommand(cmd: GrepCommandArgs): Promise<void> {
|
|
|
98
98
|
console.log(chalk.green(`Files with matches: ${result.filesWithMatches}`));
|
|
99
99
|
console.log(chalk.green(`Files searched: ${result.filesSearched}`));
|
|
100
100
|
if (result.limitReached) {
|
|
101
|
-
console.log(chalk.yellow(`
|
|
101
|
+
console.log(chalk.yellow(`Result limit reached (${cmd.limit} matches). Use --limit to show more.`));
|
|
102
102
|
}
|
|
103
103
|
console.log("");
|
|
104
104
|
|
package/src/commands/harness.ts
CHANGED
|
@@ -241,6 +241,12 @@ interface OwnerExitEvidence {
|
|
|
241
241
|
lastSignal: string | null;
|
|
242
242
|
promptAcceptedSeen: boolean;
|
|
243
243
|
completedSeen: boolean;
|
|
244
|
+
/** True only when the owner is genuinely gone (lease missing or process dead). */
|
|
245
|
+
terminal: boolean;
|
|
246
|
+
/** True when the owner process is provably alive (live lease + fresh heartbeat) but the endpoint did not route. */
|
|
247
|
+
transient: boolean;
|
|
248
|
+
/** ISO timestamp of the most recent non-terminal RPC-derived owner event, if any (observability only). */
|
|
249
|
+
lastRpcActivityAt: string | null;
|
|
244
250
|
}
|
|
245
251
|
|
|
246
252
|
async function buildOwnerExitEvidence(root: string, state: SessionState): Promise<OwnerExitEvidence> {
|
|
@@ -251,12 +257,23 @@ async function buildOwnerExitEvidence(root: string, state: SessionState): Promis
|
|
|
251
257
|
let lastSignal: string | null = null;
|
|
252
258
|
let promptAcceptedSeen = false;
|
|
253
259
|
let completedSeen = false;
|
|
260
|
+
let lastRpcActivityAt: string | null = null;
|
|
254
261
|
for (const event of events) {
|
|
255
262
|
const signal = (event.evidence as { signal?: unknown } | undefined)?.signal;
|
|
256
263
|
if (typeof signal === "string") lastSignal = signal;
|
|
257
264
|
if (event.kind === "prompt_accepted" || signal === "prompt-accepted") promptAcceptedSeen = true;
|
|
258
265
|
if (event.kind === "rpc_agent_completed" || signal === "completed") completedSeen = true;
|
|
266
|
+
// Terminal completion/failure frames are NOT owner liveness — exclude them from activity.
|
|
267
|
+
if (event.kind.startsWith("rpc_") && event.kind !== "rpc_agent_completed" && event.kind !== "rpc_agent_failed") {
|
|
268
|
+
lastRpcActivityAt = event.createdAt;
|
|
269
|
+
}
|
|
259
270
|
}
|
|
271
|
+
// Owner liveness is the lease heartbeat, never RPC frames: a "live" lease means the owner process
|
|
272
|
+
// is alive and heartbeating within TTL, so a failed endpoint call is a transient observation gap.
|
|
273
|
+
// Real owner loss (missing/dead lease) stays terminal and keeps its original reason string so
|
|
274
|
+
// existing consumers that match on the reason continue to escalate.
|
|
275
|
+
const terminal = !lease || leaseStatus === "dead";
|
|
276
|
+
const transient = leaseStatus === "live";
|
|
260
277
|
let reason = "owner-not-live";
|
|
261
278
|
if (!lease) {
|
|
262
279
|
reason = promptAcceptedSeen && !completedSeen ? "owner-exited-after-prompt-acceptance" : "owner-lease-missing";
|
|
@@ -281,6 +298,9 @@ async function buildOwnerExitEvidence(root: string, state: SessionState): Promis
|
|
|
281
298
|
lastSignal,
|
|
282
299
|
promptAcceptedSeen,
|
|
283
300
|
completedSeen,
|
|
301
|
+
terminal,
|
|
302
|
+
transient,
|
|
303
|
+
lastRpcActivityAt,
|
|
284
304
|
};
|
|
285
305
|
}
|
|
286
306
|
|
|
@@ -684,6 +704,7 @@ export default class Harness extends Command {
|
|
|
684
704
|
const handle: SessionHandle = {
|
|
685
705
|
sessionId,
|
|
686
706
|
harness,
|
|
707
|
+
mode: input.mode === "review" || input.reviewOnly === true ? "review" : "implement",
|
|
687
708
|
repo: typeof input.repo === "string" ? input.repo : null,
|
|
688
709
|
workspace,
|
|
689
710
|
branch: preflight.declaredBranch ?? preflight.actualBranch,
|
|
@@ -953,9 +974,27 @@ export default class Harness extends Command {
|
|
|
953
974
|
observation: { ...observation, lifecycle: state.lifecycle },
|
|
954
975
|
retryBudget: budget,
|
|
955
976
|
});
|
|
956
|
-
|
|
977
|
+
// A session persisted as `started` whose owner was never spawned (no lease,
|
|
978
|
+
// no endpoint, no owner-run evidence) is not a vanish — it simply never had
|
|
979
|
+
// an owner. Bootstrap a fresh owner instead of deadlocking on the missing
|
|
980
|
+
// prior endpoint (which `start` without `--detach` never records).
|
|
981
|
+
const ownerNeverStarted =
|
|
982
|
+
state.lifecycle === "started" &&
|
|
983
|
+
!beforeExit.endpointPresent &&
|
|
984
|
+
!beforeExit.promptAcceptedSeen &&
|
|
985
|
+
!beforeExit.completedSeen &&
|
|
986
|
+
beforeExit.lastEventKind === null &&
|
|
987
|
+
observation.risk !== "deleted-worktree";
|
|
988
|
+
// Bootstrapping a never-started owner is not a vanish, so it needs no vanish receipt.
|
|
989
|
+
const vanishReceiptId = ownerNeverStarted
|
|
990
|
+
? null
|
|
991
|
+
: await writeVanishReceiptForDecision(root, state, observation, decision.classification);
|
|
992
|
+
// A never-started owner has no in-flight work to preserve, so bootstrapping it does not
|
|
993
|
+
// depend on the vanish classifier's `ownerRequired` verdict — that gate exists to protect a
|
|
994
|
+
// vanished owner's worktree. Without this, a session started in a non-git workspace (git
|
|
995
|
+
// delta `unknown` → classifier `human-check` with `ownerRequired: false`) would stay stuck.
|
|
957
996
|
const restoredOwner =
|
|
958
|
-
decision.ownerRequired && beforeExit.endpointPresent
|
|
997
|
+
ownerNeverStarted || (decision.ownerRequired && beforeExit.endpointPresent)
|
|
959
998
|
? await this.#spawnDetachedOwner(root, sessionId, state.handle.workspace)
|
|
960
999
|
: null;
|
|
961
1000
|
if (restoredOwner?.live) {
|
|
@@ -968,7 +1007,7 @@ export default class Harness extends Command {
|
|
|
968
1007
|
writeJson(
|
|
969
1008
|
buildResponse(state, true, {
|
|
970
1009
|
pending: false,
|
|
971
|
-
restoredOwner: true,
|
|
1010
|
+
...(ownerNeverStarted ? { bootstrappedOwner: true } : { restoredOwner: true }),
|
|
972
1011
|
decision,
|
|
973
1012
|
observation: { ...observation, lifecycle: state.lifecycle, ownerLive: true },
|
|
974
1013
|
ownerExit: beforeExit,
|
|
@@ -12,6 +12,7 @@ export default class Ultragoal extends Command {
|
|
|
12
12
|
static description = "Run native GJC Ultragoal workflow commands";
|
|
13
13
|
static strict = false;
|
|
14
14
|
static examples = ["$ gjc ultragoal status --json"];
|
|
15
|
+
static delegateHelp = true;
|
|
15
16
|
|
|
16
17
|
async run(): Promise<void> {
|
|
17
18
|
const shouldActivateGoalMode = isUltragoalCreateGoalsInvocation(this.argv);
|