@gajae-code/coding-agent 0.12.8 → 0.12.10
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 +29 -0
- package/dist/types/cli/notify-cli.d.ts +1 -0
- package/dist/types/commands/sdk.d.ts +23 -0
- package/dist/types/discovery/agents-md.d.ts +11 -1
- package/dist/types/modes/types.d.ts +6 -0
- package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
- package/dist/types/sdk/bus/notification-service.d.ts +23 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +2 -2
- package/dist/types/session/internal/managed-session-storage.d.ts +8 -0
- package/dist/types/session/internal/native-publish-outcome.d.ts +2 -2
- package/dist/types/system-prompt.d.ts +12 -1
- package/dist/types/tools/composer-bash-policy.d.ts +9 -1
- package/package.json +8 -8
- package/scripts/verify-gjc-sdk-canonicalization.ts +14 -1
- package/src/cli/notify-cli.ts +12 -6
- package/src/commands/sdk.ts +54 -0
- package/src/config/model-profiles.ts +8 -0
- package/src/config/model-registry.ts +4 -1
- package/src/discovery/agents-md.ts +76 -19
- package/src/gjc-runtime/psmux-detect.ts +12 -16
- package/src/gjc-runtime/tmux-sessions.ts +8 -1
- package/src/internal-urls/docs-index.generated.ts +4 -3
- package/src/modes/components/extensions/inspector-panel.ts +9 -5
- package/src/modes/controllers/selector-controller.ts +61 -43
- package/src/modes/print-mode.ts +3 -0
- package/src/modes/types.ts +6 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/sdk/bus/daemon-paths.ts +2 -0
- package/src/sdk/bus/notification-service.ts +140 -7
- package/src/sdk/bus/telegram-daemon-contract.ts +2 -2
- package/src/sdk/session.ts +24 -7
- package/src/session/agent-session.ts +65 -13
- package/src/session/internal/managed-session-storage.ts +36 -2
- package/src/session/internal/native-publish-outcome.ts +12 -2
- package/src/slash-commands/builtin-registry.ts +35 -11
- package/src/system-prompt.ts +28 -15
- package/src/tools/bash.ts +3 -1
- package/src/tools/composer-bash-policy.ts +21 -5
- package/src/utils/command-args.ts +19 -30
- package/src/utils/lang-from-path.ts +2 -4
|
@@ -5,56 +5,113 @@
|
|
|
5
5
|
* This handles AGENTS.md files that live in project root (not in config directories
|
|
6
6
|
* like .OpenAI code backend/ or .gemini/, which are handled by their respective providers).
|
|
7
7
|
*/
|
|
8
|
+
import * as fs from "node:fs/promises";
|
|
8
9
|
import * as path from "node:path";
|
|
9
10
|
import { registerProvider } from "../capability";
|
|
10
11
|
import { type ContextFile, contextFileCapability } from "../capability/context-file";
|
|
11
|
-
import { readFile } from "../capability/fs";
|
|
12
12
|
import type { LoadContext, LoadResult } from "../capability/types";
|
|
13
13
|
import { calculateDepth, createSourceMeta } from "./helpers";
|
|
14
14
|
|
|
15
15
|
const PROVIDER_ID = "agents-md";
|
|
16
16
|
const DISPLAY_NAME = "AGENTS.md";
|
|
17
|
+
// Bound hostile ancestor walks and instruction payloads before they reach prompt assembly.
|
|
18
|
+
const MAX_ANCESTOR_DIRECTORIES = 32;
|
|
19
|
+
const MAX_FILE_BYTES = 64 * 1024;
|
|
20
|
+
const MAX_AGGREGATE_BYTES = 256 * 1024;
|
|
21
|
+
const DIRECTORY_LIMIT_WARNING = "AGENTS.md discovery stopped after scanning 32 ancestor directories.";
|
|
22
|
+
const FILE_LIMIT_WARNING = "Skipped one or more AGENTS.md files that exceed the 64 KiB limit.";
|
|
23
|
+
const AGGREGATE_LIMIT_WARNING = "Skipped one or more AGENTS.md files that exceed the 256 KiB aggregate limit.";
|
|
24
|
+
|
|
25
|
+
export type AgentsMdReader = (
|
|
26
|
+
filePath: string,
|
|
27
|
+
maxBytes: number,
|
|
28
|
+
) => Promise<{ content: string | null; byteLength: number; tooLarge: boolean }>;
|
|
29
|
+
|
|
30
|
+
async function readBoundedAgentsMdFile(
|
|
31
|
+
filePath: string,
|
|
32
|
+
maxBytes: number,
|
|
33
|
+
): Promise<{ content: string | null; byteLength: number; tooLarge: boolean }> {
|
|
34
|
+
try {
|
|
35
|
+
const file = await fs.open(filePath, fs.constants.O_RDONLY | fs.constants.O_NONBLOCK);
|
|
36
|
+
try {
|
|
37
|
+
const stats = await file.stat();
|
|
38
|
+
if (!stats.isFile()) return { content: null, byteLength: 0, tooLarge: false };
|
|
39
|
+
const bytes = Buffer.alloc(maxBytes + 1);
|
|
40
|
+
let bytesRead = 0;
|
|
41
|
+
while (bytesRead < bytes.length) {
|
|
42
|
+
const result = await file.read(bytes, bytesRead, bytes.length - bytesRead, bytesRead);
|
|
43
|
+
if (result.bytesRead === 0) break;
|
|
44
|
+
bytesRead += result.bytesRead;
|
|
45
|
+
}
|
|
46
|
+
if (bytesRead > maxBytes) return { content: null, byteLength: bytesRead, tooLarge: true };
|
|
47
|
+
return {
|
|
48
|
+
content: new TextDecoder().decode(bytes.subarray(0, bytesRead)),
|
|
49
|
+
byteLength: bytesRead,
|
|
50
|
+
tooLarge: false,
|
|
51
|
+
};
|
|
52
|
+
} finally {
|
|
53
|
+
await file.close();
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
return { content: null, byteLength: 0, tooLarge: false };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
17
59
|
|
|
18
60
|
/**
|
|
19
61
|
* Load standalone AGENTS.md files.
|
|
20
62
|
*/
|
|
21
|
-
async function loadAgentsMd(
|
|
63
|
+
export async function loadAgentsMd(
|
|
64
|
+
ctx: LoadContext,
|
|
65
|
+
readCandidate: AgentsMdReader = readBoundedAgentsMdFile,
|
|
66
|
+
): Promise<LoadResult<ContextFile>> {
|
|
22
67
|
const items: ContextFile[] = [];
|
|
23
68
|
const warnings: string[] = [];
|
|
24
|
-
|
|
25
|
-
// Walk up from cwd looking for AGENTS.md files
|
|
26
69
|
let current = ctx.cwd;
|
|
70
|
+
let scannedDirectories = 0;
|
|
71
|
+
let aggregateBytes = 0;
|
|
72
|
+
let omittedOversizedFile = false;
|
|
73
|
+
let omittedAggregateFile = false;
|
|
27
74
|
|
|
28
75
|
while (true) {
|
|
76
|
+
scannedDirectories += 1;
|
|
29
77
|
const candidate = path.join(current, "AGENTS.md");
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
if (content !== null) {
|
|
33
|
-
const parent = path.dirname(candidate);
|
|
34
|
-
const baseName = parent.split(path.sep).pop() ?? "";
|
|
78
|
+
const parent = path.dirname(candidate);
|
|
79
|
+
const baseName = parent.split(path.sep).pop() ?? "";
|
|
35
80
|
|
|
36
|
-
|
|
81
|
+
if (!baseName.startsWith(".")) {
|
|
82
|
+
const remainingAggregateBytes = MAX_AGGREGATE_BYTES - aggregateBytes;
|
|
83
|
+
const allowedBytes = Math.min(MAX_FILE_BYTES, remainingAggregateBytes);
|
|
84
|
+
const result = await readCandidate(candidate, allowedBytes);
|
|
85
|
+
if (result.tooLarge) {
|
|
86
|
+
if (allowedBytes < MAX_FILE_BYTES) omittedAggregateFile = true;
|
|
87
|
+
else omittedOversizedFile = true;
|
|
88
|
+
} else if (result.content !== null) {
|
|
37
89
|
const fileDir = path.dirname(candidate);
|
|
38
|
-
const calculatedDepth = calculateDepth(ctx.cwd, fileDir, path.sep);
|
|
39
|
-
|
|
40
90
|
items.push({
|
|
41
91
|
path: candidate,
|
|
42
|
-
content,
|
|
92
|
+
content: result.content,
|
|
43
93
|
level: "project",
|
|
44
|
-
depth:
|
|
94
|
+
depth: calculateDepth(ctx.cwd, fileDir, path.sep),
|
|
45
95
|
_source: createSourceMeta(PROVIDER_ID, candidate, "project"),
|
|
46
96
|
});
|
|
97
|
+
aggregateBytes += result.byteLength;
|
|
47
98
|
}
|
|
48
99
|
}
|
|
49
100
|
|
|
50
|
-
|
|
101
|
+
const stopDirectory = ctx.repoRoot ?? ctx.home;
|
|
102
|
+
if (current === stopDirectory) break;
|
|
103
|
+
if (scannedDirectories === MAX_ANCESTOR_DIRECTORIES) {
|
|
104
|
+
warnings.push(DIRECTORY_LIMIT_WARNING);
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
51
107
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
current = parent;
|
|
108
|
+
const parentDirectory = path.dirname(current);
|
|
109
|
+
if (parentDirectory === current) break;
|
|
110
|
+
current = parentDirectory;
|
|
56
111
|
}
|
|
57
112
|
|
|
113
|
+
if (omittedOversizedFile) warnings.push(FILE_LIMIT_WARNING);
|
|
114
|
+
if (omittedAggregateFile) warnings.push(AGGREGATE_LIMIT_WARNING);
|
|
58
115
|
return { items, warnings };
|
|
59
116
|
}
|
|
60
117
|
|
|
@@ -324,25 +324,21 @@ export function resolveGjcTmuxBinary(options: ResolveGjcTmuxBinaryOptions = {}):
|
|
|
324
324
|
return { command: explicit, isPsmux, viaExplicitOverride: true };
|
|
325
325
|
}
|
|
326
326
|
if (platform === "win32") {
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
const executablePath = resolveBinaryPath(candidate);
|
|
327
|
+
for (const command of ["psmux", "pmux"] as const) {
|
|
328
|
+
const executablePath = resolveBinaryPath(command);
|
|
330
329
|
if (!executablePath) continue;
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
if (!identity)
|
|
335
|
-
throw new Error(`gjc_tmux_provider_ambiguous: Windows ${candidate} executable identity is unavailable`);
|
|
336
|
-
candidates.push({ command: candidate, identity });
|
|
330
|
+
if (!activeExecutableIdentityResolver(executablePath))
|
|
331
|
+
throw new Error(`gjc_tmux_provider_ambiguous: Windows ${command} executable identity is unavailable`);
|
|
332
|
+
return { command, isPsmux: true, viaExplicitOverride: false };
|
|
337
333
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
return { command: candidates[0]!.command, isPsmux: true, viaExplicitOverride: false };
|
|
334
|
+
const tmuxPath = resolveBinaryPath("tmux");
|
|
335
|
+
if (tmuxPath) {
|
|
336
|
+
const isPsmux = outputMentionsPsmux(probeVersionOutput(tmuxPath, runner));
|
|
337
|
+
if (isPsmux && !activeExecutableIdentityResolver(tmuxPath))
|
|
338
|
+
throw new Error("gjc_tmux_provider_ambiguous: Windows tmux executable identity is unavailable");
|
|
339
|
+
return { command: "tmux", isPsmux, viaExplicitOverride: false };
|
|
345
340
|
}
|
|
341
|
+
return { command: "tmux", isPsmux: false, viaExplicitOverride: false };
|
|
346
342
|
}
|
|
347
343
|
const tmuxPath = resolveBinaryPath("tmux");
|
|
348
344
|
if (tmuxPath) {
|
|
@@ -228,7 +228,7 @@ function runTmux(
|
|
|
228
228
|
);
|
|
229
229
|
}
|
|
230
230
|
if (result.exitCode === 0) return result.stdout?.toString() ?? "";
|
|
231
|
-
throw new Error(result.stderr?.toString().trim() ||
|
|
231
|
+
throw new Error(result.stderr?.toString().trim() || `${tmuxCommand} ${args.join(" ")} failed`);
|
|
232
232
|
}
|
|
233
233
|
function normalizeExactTmuxTarget(sessionTarget: string, env: NodeJS.ProcessEnv, kind: "session" | "option"): string {
|
|
234
234
|
if (sessionTarget.startsWith("$")) return sessionTarget;
|
|
@@ -407,6 +407,13 @@ function psmuxAuthorityEnvironments(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv[]
|
|
|
407
407
|
if (authorities.length > 0)
|
|
408
408
|
return authorities.map(authority => environmentForProviderAuthority(env, authority));
|
|
409
409
|
}
|
|
410
|
+
if (!ambient.viaExplicitOverride && !ambientAvailable) {
|
|
411
|
+
throw new Error(
|
|
412
|
+
"gjc_tmux_provider_unavailable — GJC searched for psmux, pmux, and tmux on PATH. " +
|
|
413
|
+
"Install psmux from https://github.com/psmux/psmux for native Windows support, use WSL with real tmux, " +
|
|
414
|
+
"or set GJC_TMUX_COMMAND (and GJC_PSMUX_COMMAND when selecting a psmux compatibility alias).",
|
|
415
|
+
);
|
|
416
|
+
}
|
|
410
417
|
}
|
|
411
418
|
if (ambient.isPsmux) throw new Error("gjc_tmux_provider_authority_unavailable");
|
|
412
419
|
return [env];
|