@gajae-code/coding-agent 0.7.2 → 0.7.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 +38 -0
- package/dist/types/cli/mcp-cli.d.ts +25 -0
- package/dist/types/cli.d.ts +6 -0
- package/dist/types/commands/mcp.d.ts +70 -0
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/deep-interview/plaintext-gate-guard.d.ts +11 -0
- package/dist/types/modes/components/custom-editor.d.ts +1 -1
- package/dist/types/modes/components/model-selector.d.ts +2 -0
- package/dist/types/modes/components/status-line/git-utils.d.ts +6 -0
- package/dist/types/modes/theme/defaults/index.d.ts +99 -0
- package/dist/types/notifications/operator-runtime.d.ts +52 -0
- package/dist/types/notifications/telegram-daemon.d.ts +54 -16
- package/dist/types/notifications/topic-registry.d.ts +2 -0
- package/dist/types/tools/composer-bash-policy.d.ts +14 -0
- package/dist/types/web/insane/url-guard.d.ts +6 -3
- package/dist/types/web/scrapers/types.d.ts +5 -0
- package/dist/types/web/scrapers/utils.d.ts +7 -1
- package/package.json +7 -7
- package/src/cli/mcp-cli.ts +272 -0
- package/src/cli.ts +6 -2
- package/src/commands/mcp.ts +117 -0
- package/src/config/keybindings.ts +2 -2
- package/src/deep-interview/plaintext-gate-guard.ts +94 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +4 -3
- package/src/defaults/gjc/skills/team/SKILL.md +3 -2
- package/src/extensibility/extensions/runner.ts +1 -0
- package/src/gjc-runtime/tmux-common.ts +3 -1
- package/src/gjc-runtime/ultragoal-guard.ts +25 -8
- package/src/hooks/skill-state.ts +57 -0
- package/src/internal-urls/docs-index.generated.ts +10 -7
- package/src/modes/bridge/bridge-mode.ts +11 -0
- package/src/modes/components/custom-editor.ts +2 -0
- package/src/modes/components/footer.ts +2 -3
- package/src/modes/components/model-selector.ts +12 -0
- package/src/modes/components/status-line/git-utils.ts +25 -0
- package/src/modes/components/status-line.ts +10 -11
- package/src/modes/components/welcome.ts +2 -3
- package/src/modes/controllers/selector-controller.ts +3 -0
- package/src/modes/interactive-mode.ts +2 -1
- package/src/modes/shared/agent-wire/scopes.ts +1 -1
- package/src/modes/theme/defaults/gruvbox-dark.json +99 -0
- package/src/modes/theme/defaults/index.ts +2 -0
- package/src/notifications/operator-runtime.ts +171 -0
- package/src/notifications/telegram-daemon.ts +347 -251
- package/src/notifications/topic-registry.ts +5 -0
- package/src/slash-commands/helpers/parse.ts +2 -1
- package/src/tools/bash.ts +9 -0
- package/src/tools/composer-bash-policy.ts +96 -0
- package/src/tools/fetch.ts +18 -2
- package/src/web/insane/url-guard.ts +18 -14
- package/src/web/scrapers/types.ts +143 -45
- package/src/web/scrapers/utils.ts +70 -19
|
@@ -65,15 +65,17 @@ function isKnownUltragoalObjective(currentObjective: string): boolean {
|
|
|
65
65
|
);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
async function ultragoalReadPaths(cwd: string): Promise<UltragoalPaths> {
|
|
68
|
+
async function ultragoalReadPaths(cwd: string): Promise<{ paths: UltragoalPaths; sessionId: string | null }> {
|
|
69
69
|
const envSessionId = process.env.GJC_SESSION_ID?.trim();
|
|
70
|
-
if (envSessionId) return getUltragoalPaths(cwd, envSessionId);
|
|
70
|
+
if (envSessionId) return { paths: getUltragoalPaths(cwd, envSessionId), sessionId: envSessionId };
|
|
71
71
|
try {
|
|
72
72
|
const session = await resolveGjcSessionForRead(cwd, { envSessionId: process.env.GJC_SESSION_ID });
|
|
73
|
-
return getUltragoalPaths(cwd, session.gjcSessionId);
|
|
73
|
+
return { paths: getUltragoalPaths(cwd, session.gjcSessionId), sessionId: session.gjcSessionId };
|
|
74
74
|
} catch (error) {
|
|
75
75
|
if (error instanceof SessionResolutionError && error.code === "no_session") {
|
|
76
|
-
|
|
76
|
+
// No session could be resolved (no env, no auto-detectable active session).
|
|
77
|
+
// Surface the null session id so callers can decide; ask-guard treats it as inactive.
|
|
78
|
+
return { paths: getUltragoalPaths(cwd, null), sessionId: null };
|
|
77
79
|
}
|
|
78
80
|
throw error;
|
|
79
81
|
}
|
|
@@ -82,7 +84,7 @@ async function ultragoalReadPaths(cwd: string): Promise<UltragoalPaths> {
|
|
|
82
84
|
async function hasDurableUltragoalState(cwd: string): Promise<boolean> {
|
|
83
85
|
let paths: UltragoalPaths;
|
|
84
86
|
try {
|
|
85
|
-
paths = await ultragoalReadPaths(cwd);
|
|
87
|
+
({ paths } = await ultragoalReadPaths(cwd));
|
|
86
88
|
} catch (error) {
|
|
87
89
|
if (error instanceof SessionResolutionError) return true;
|
|
88
90
|
throw error;
|
|
@@ -368,14 +370,26 @@ export async function readUltragoalVerificationState(input: {
|
|
|
368
370
|
|
|
369
371
|
export async function isUltragoalAskBlocked(cwd: string): Promise<UltragoalAskBlockDiagnostic> {
|
|
370
372
|
let paths: UltragoalPaths;
|
|
373
|
+
let sessionId: string | null;
|
|
371
374
|
try {
|
|
372
|
-
paths = await ultragoalReadPaths(cwd);
|
|
375
|
+
({ paths, sessionId } = await ultragoalReadPaths(cwd));
|
|
373
376
|
} catch (error) {
|
|
374
377
|
return activeAskDiagnostic({
|
|
375
378
|
reason: `Unable to resolve durable Ultragoal state: ${error instanceof Error ? error.message : String(error)}`,
|
|
376
379
|
source: "durable_state_unreadable",
|
|
377
380
|
});
|
|
378
381
|
}
|
|
382
|
+
// Ultragoal state is session-scoped. When no session can be resolved (no env,
|
|
383
|
+
// no auto-detectable active session) there is no active run to protect, so the
|
|
384
|
+
// ask guard must fall open rather than block on legacy/global durable state.
|
|
385
|
+
if (sessionId === null) {
|
|
386
|
+
return inactiveAskDiagnostic({
|
|
387
|
+
reason: "No active GJC session resolved; ultragoal is inactive.",
|
|
388
|
+
source: "absent",
|
|
389
|
+
goalsPath: paths.goalsPath,
|
|
390
|
+
ledgerPath: paths.ledgerPath,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
379
393
|
try {
|
|
380
394
|
await fs.stat(paths.dir);
|
|
381
395
|
} catch (error) {
|
|
@@ -398,8 +412,8 @@ export async function isUltragoalAskBlocked(cwd: string): Promise<UltragoalAskBl
|
|
|
398
412
|
let plan: UltragoalPlan | null;
|
|
399
413
|
let ledger: UltragoalLedgerEvent[];
|
|
400
414
|
try {
|
|
401
|
-
plan = await readUltragoalPlan(cwd);
|
|
402
|
-
ledger = await readUltragoalLedger(cwd);
|
|
415
|
+
plan = await readUltragoalPlan(cwd, sessionId);
|
|
416
|
+
ledger = await readUltragoalLedger(cwd, sessionId);
|
|
403
417
|
} catch (error) {
|
|
404
418
|
return activeAskDiagnostic({
|
|
405
419
|
reason: `Unable to read durable Ultragoal state: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -409,6 +423,9 @@ export async function isUltragoalAskBlocked(cwd: string): Promise<UltragoalAskBl
|
|
|
409
423
|
});
|
|
410
424
|
}
|
|
411
425
|
if (!plan) {
|
|
426
|
+
// goals.json absent or empty while the state dir exists is an inconsistent
|
|
427
|
+
// durable state, not a clean "no run". Fail closed so the pause guard (which
|
|
428
|
+
// relies on this `durable_state_unreadable` signal) keeps blocking give-ups.
|
|
412
429
|
return activeAskDiagnostic({
|
|
413
430
|
reason: "Durable .gjc/ultragoal state exists but goals.json is missing or empty.",
|
|
414
431
|
source: "durable_state_unreadable",
|
package/src/hooks/skill-state.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { logger } from "@gajae-code/utils";
|
|
4
4
|
import type { SkillDiscoverySettings } from "../config/skill-settings-defaults";
|
|
5
|
+
import { detectDeepInterviewPlaintextAskLeak } from "../deep-interview/plaintext-gate-guard";
|
|
5
6
|
import { activeSnapshotPath, modeStatePath as sessionModeStatePath } from "../gjc-runtime/session-layout";
|
|
6
7
|
import { resolveGjcSessionForRead } from "../gjc-runtime/session-resolution";
|
|
7
8
|
import { ModeStateSchema, SkillActiveStateSchema } from "../gjc-runtime/state-schema";
|
|
@@ -675,6 +676,52 @@ async function readCurrentGoalObjectiveFromSessionFile(sessionFile: string | und
|
|
|
675
676
|
return typeof objective === "string" && objective.trim().length > 0 ? objective.trim() : null;
|
|
676
677
|
}
|
|
677
678
|
|
|
679
|
+
async function readLatestAssistantTextFromSessionFile(sessionFile: string | undefined): Promise<string | null> {
|
|
680
|
+
const trimmed = sessionFile?.trim();
|
|
681
|
+
if (!trimmed) return null;
|
|
682
|
+
let entries: SessionEntry[];
|
|
683
|
+
try {
|
|
684
|
+
entries = (await loadEntriesFromFile(trimmed)).filter((entry): entry is SessionEntry => entry.type !== "session");
|
|
685
|
+
} catch {
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
if (entries.length === 0) return null;
|
|
689
|
+
const context = buildSessionContext(entries);
|
|
690
|
+
for (let index = context.messages.length - 1; index >= 0; index--) {
|
|
691
|
+
const message = context.messages[index];
|
|
692
|
+
if (message?.role !== "assistant") continue;
|
|
693
|
+
const text = message.content
|
|
694
|
+
.filter(block => block.type === "text")
|
|
695
|
+
.map(block => block.text)
|
|
696
|
+
.join("");
|
|
697
|
+
const trimmedText = text.trim();
|
|
698
|
+
return trimmedText.length > 0 ? trimmedText : null;
|
|
699
|
+
}
|
|
700
|
+
return null;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
async function shouldRescueDeepInterviewPlaintextAskLeak(
|
|
704
|
+
skill: GjcWorkflowSkill,
|
|
705
|
+
state: ModeState | null,
|
|
706
|
+
cwd: string,
|
|
707
|
+
sessionFile: string | undefined,
|
|
708
|
+
): Promise<boolean> {
|
|
709
|
+
if (skill !== "deep-interview") return false;
|
|
710
|
+
if (state?.active !== true) return false;
|
|
711
|
+
const phase = String(state.current_phase ?? "")
|
|
712
|
+
.trim()
|
|
713
|
+
.toLowerCase();
|
|
714
|
+
if (DEEP_INTERVIEW_ABORT_PHASES.has(phase)) return false;
|
|
715
|
+
if (await deepInterviewSpecCrystallized(state, cwd)) return false;
|
|
716
|
+
const latestAssistantText = await readLatestAssistantTextFromSessionFile(sessionFile);
|
|
717
|
+
if (!latestAssistantText) return false;
|
|
718
|
+
return detectDeepInterviewPlaintextAskLeak(latestAssistantText) !== null;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function buildDeepInterviewPlaintextAskLeakMessage(statePath: string): string {
|
|
722
|
+
return `GJC deep-interview emitted a Deep Interview question/options block as plain text (${statePath}). It must not wait for a prose answer. Continue immediately by calling the ask tool with the Restate gate question and options: Yes, crystallize; Adjust wording; Missing scope; plus free text/custom input.`;
|
|
723
|
+
}
|
|
724
|
+
|
|
678
725
|
export async function buildActiveUltragoalPromptContext(input: UserPromptSubmitStateInput): Promise<string | null> {
|
|
679
726
|
const resolvedSessionId = await resolveBoundarySessionId(input.cwd, input.sessionId);
|
|
680
727
|
const visibleModeState = await readVisibleModeState(input.cwd, "ultragoal", resolvedSessionId, input.stateDir);
|
|
@@ -756,6 +803,16 @@ export async function buildSkillStopOutput(input: StopHookInput): Promise<Record
|
|
|
756
803
|
systemMessage: recoveryMessage,
|
|
757
804
|
};
|
|
758
805
|
}
|
|
806
|
+
if (await shouldRescueDeepInterviewPlaintextAskLeak(entry.skill, modeState, input.cwd, input.sessionFile)) {
|
|
807
|
+
const statePath = modeStatePath(input.cwd, entry.skill, resolvedSessionId);
|
|
808
|
+
const rescueMessage = buildDeepInterviewPlaintextAskLeakMessage(statePath);
|
|
809
|
+
return {
|
|
810
|
+
decision: "block",
|
|
811
|
+
reason: rescueMessage,
|
|
812
|
+
stopReason: "gjc_skill_deep_interview_plaintext_ask_leak",
|
|
813
|
+
systemMessage: rescueMessage,
|
|
814
|
+
};
|
|
815
|
+
}
|
|
759
816
|
if (modeStateReleasesStop(modeState, handoffRequired)) {
|
|
760
817
|
// A mode-state that claims it releases the Stop block must agree with
|
|
761
818
|
// authoritative durable state. If a stale/incoherent mode-state would
|