@f5-sales-demo/xcsh 19.84.0 → 19.85.0
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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5-sales-demo/xcsh",
|
|
4
|
-
"version": "19.
|
|
4
|
+
"version": "19.85.0",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/f5-sales-demo/xcsh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -56,13 +56,13 @@
|
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
58
58
|
"@mozilla/readability": "^0.6",
|
|
59
|
-
"@f5-sales-demo/xcsh-stats": "19.
|
|
60
|
-
"@f5-sales-demo/pi-agent-core": "19.
|
|
61
|
-
"@f5-sales-demo/pi-ai": "19.
|
|
62
|
-
"@f5-sales-demo/pi-natives": "19.
|
|
63
|
-
"@f5-sales-demo/pi-resource-management": "19.
|
|
64
|
-
"@f5-sales-demo/pi-tui": "19.
|
|
65
|
-
"@f5-sales-demo/pi-utils": "19.
|
|
59
|
+
"@f5-sales-demo/xcsh-stats": "19.85.0",
|
|
60
|
+
"@f5-sales-demo/pi-agent-core": "19.85.0",
|
|
61
|
+
"@f5-sales-demo/pi-ai": "19.85.0",
|
|
62
|
+
"@f5-sales-demo/pi-natives": "19.85.0",
|
|
63
|
+
"@f5-sales-demo/pi-resource-management": "19.85.0",
|
|
64
|
+
"@f5-sales-demo/pi-tui": "19.85.0",
|
|
65
|
+
"@f5-sales-demo/pi-utils": "19.85.0",
|
|
66
66
|
"@sinclair/typebox": "^0.34",
|
|
67
67
|
"@xterm/headless": "^6.0",
|
|
68
68
|
"ajv": "^8.20",
|
|
@@ -555,7 +555,18 @@ function titleFromUrl(url: string): string {
|
|
|
555
555
|
}
|
|
556
556
|
}
|
|
557
557
|
|
|
558
|
-
|
|
558
|
+
/**
|
|
559
|
+
* Trailing characters a bare-URL match may greedily swallow at a markdown/prose
|
|
560
|
+
* boundary — markdown emphasis (`*_~`) and sentence/wrap punctuation. A real URL
|
|
561
|
+
* effectively never ends in these, so trimming them yields the intended link
|
|
562
|
+
* (e.g. `**https://…/llms.txt**` → `https://…/llms.txt`). The markdown-link branch
|
|
563
|
+
* is bounded by its closing `)` and needs no trimming.
|
|
564
|
+
*/
|
|
565
|
+
function trimTrailingMarkup(url: string): string {
|
|
566
|
+
return url.replace(/[*_~,.;:!?'")\]]+$/, "");
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
export function extractReferences(msg: AssistantMessage): ChatReference[] {
|
|
559
570
|
const refs: ChatReference[] = [];
|
|
560
571
|
const seen = new Set<string>();
|
|
561
572
|
|
|
@@ -572,7 +583,7 @@ function extractReferences(msg: AssistantMessage): ChatReference[] {
|
|
|
572
583
|
|
|
573
584
|
const bareUrlRegex = /(?<!\()(https?:\/\/[^\s)>\]]+)/g;
|
|
574
585
|
for (let match = bareUrlRegex.exec(block.text); match !== null; match = bareUrlRegex.exec(block.text)) {
|
|
575
|
-
const url = match[1];
|
|
586
|
+
const url = trimTrailingMarkup(match[1]);
|
|
576
587
|
if (seen.has(url)) continue;
|
|
577
588
|
seen.add(url);
|
|
578
589
|
refs.push({ kind: classifyReferenceKind(url), title: titleFromUrl(url), url });
|
|
@@ -17,17 +17,17 @@ export interface BuildInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BUILD_INFO: BuildInfo = {
|
|
20
|
-
"version": "19.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "19.85.0",
|
|
21
|
+
"commit": "6e7e1972db24c3151be8dceff0e37d424d674edd",
|
|
22
|
+
"shortCommit": "6e7e197",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v19.
|
|
25
|
-
"commitDate": "2026-07-
|
|
26
|
-
"buildDate": "2026-07-
|
|
24
|
+
"tag": "v19.85.0",
|
|
25
|
+
"commitDate": "2026-07-23T16:36:07Z",
|
|
26
|
+
"buildDate": "2026-07-23T16:59:32.962Z",
|
|
27
27
|
"dirty": true,
|
|
28
28
|
"prNumber": "",
|
|
29
29
|
"repoUrl": "https://github.com/f5-sales-demo/xcsh",
|
|
30
30
|
"repoSlug": "f5-sales-demo/xcsh",
|
|
31
|
-
"commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/
|
|
32
|
-
"releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v19.
|
|
31
|
+
"commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/6e7e1972db24c3151be8dceff0e37d424d674edd",
|
|
32
|
+
"releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v19.85.0"
|
|
33
33
|
};
|
package/src/sdk.ts
CHANGED
|
@@ -112,6 +112,7 @@ import { SessionManager } from "./session/session-manager";
|
|
|
112
112
|
import { closeAllConnections } from "./ssh/connection-manager";
|
|
113
113
|
import { unmountAll } from "./ssh/sshfs-mount";
|
|
114
114
|
import {
|
|
115
|
+
buildAgentsMdSearch,
|
|
115
116
|
buildSystemPrompt as buildSystemPromptInternal,
|
|
116
117
|
buildSystemPromptToolMetadata,
|
|
117
118
|
loadProjectContextFiles as loadContextFilesInternal,
|
|
@@ -965,6 +966,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
965
966
|
async () => options.contextFiles ?? (await discoverContextFiles(cwd, agentDir)),
|
|
966
967
|
);
|
|
967
968
|
|
|
969
|
+
// Walk the CWD for nested XCSH.md ONCE per session — the walk is bounded but not
|
|
970
|
+
// free, so hoisting it here keeps every tool-refresh prompt rebuild off the tree
|
|
971
|
+
// (a large cwd like $HOME must never re-stall on set_host_tools). #2245.
|
|
972
|
+
const agentsMdSearch = await logger.time("buildAgentsMdSearch", buildAgentsMdSearch, cwd);
|
|
973
|
+
|
|
968
974
|
let agent: Agent;
|
|
969
975
|
let session!: AgentSession;
|
|
970
976
|
let hasSession = false;
|
|
@@ -1596,6 +1602,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1596
1602
|
cwd,
|
|
1597
1603
|
skills,
|
|
1598
1604
|
contextFiles,
|
|
1605
|
+
agentsMdSearch,
|
|
1599
1606
|
tools: promptTools,
|
|
1600
1607
|
toolNames,
|
|
1601
1608
|
rules: rulebookRules,
|
|
@@ -1626,6 +1633,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1626
1633
|
cwd,
|
|
1627
1634
|
skills,
|
|
1628
1635
|
contextFiles,
|
|
1636
|
+
agentsMdSearch,
|
|
1629
1637
|
tools: promptTools,
|
|
1630
1638
|
toolNames,
|
|
1631
1639
|
rules: rulebookRules,
|
package/src/system-prompt.ts
CHANGED
|
@@ -108,15 +108,30 @@ const AGENTS_MD_MIN_DEPTH = 1;
|
|
|
108
108
|
const AGENTS_MD_MAX_DEPTH = 4;
|
|
109
109
|
const AGENTS_MD_LIMIT = 200;
|
|
110
110
|
const SYSTEM_PROMPT_PREP_TIMEOUT_MS = 5000;
|
|
111
|
+
// The walk's `limit` caps DISCOVERED files, not directories VISITED — so a dir
|
|
112
|
+
// with ~no XCSH.md (e.g. serving from $HOME) would otherwise traverse the entire
|
|
113
|
+
// tree to AGENTS_MD_MAX_DEPTH and stall prep. These bound the traversal itself:
|
|
114
|
+
// a directory-visit budget AND a wall-clock deadline, both well inside the 5s
|
|
115
|
+
// prep timeout, so discovery stays fast regardless of how few matches exist.
|
|
116
|
+
const AGENTS_MD_MAX_DIRS = 4000;
|
|
117
|
+
const AGENTS_MD_WALK_BUDGET_MS = 1500;
|
|
111
118
|
const AGENTS_MD_EXCLUDED_DIRS = new Set(["node_modules", ".git"]);
|
|
112
119
|
|
|
113
|
-
|
|
120
|
+
/** A cited context-file search result (the nested XCSH.md files under the cwd). */
|
|
121
|
+
export interface AgentsMdSearch {
|
|
114
122
|
scopePath: string;
|
|
115
123
|
limit: number;
|
|
116
124
|
pattern: string;
|
|
117
125
|
files: string[];
|
|
118
126
|
}
|
|
119
127
|
|
|
128
|
+
/** Mutable traversal budget shared across the recursive walk. */
|
|
129
|
+
interface WalkBudget {
|
|
130
|
+
readonly maxDirs: number;
|
|
131
|
+
readonly deadline: number;
|
|
132
|
+
dirsVisited: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
120
135
|
function normalizePath(value: string): string {
|
|
121
136
|
return value.replace(/\\/g, "/");
|
|
122
137
|
}
|
|
@@ -131,11 +146,24 @@ async function collectAgentsMdFiles(
|
|
|
131
146
|
dir: string,
|
|
132
147
|
depth: number,
|
|
133
148
|
limit: number,
|
|
149
|
+
maxDepth: number,
|
|
134
150
|
discovered: Set<string>,
|
|
151
|
+
budget: WalkBudget,
|
|
135
152
|
): Promise<void> {
|
|
136
|
-
|
|
153
|
+
// Stop on any bound: depth, enough matches, the directory-visit budget, or the
|
|
154
|
+
// wall-clock deadline. The last two keep a match-poor tree (e.g. $HOME) bounded.
|
|
155
|
+
if (
|
|
156
|
+
depth > maxDepth ||
|
|
157
|
+
discovered.size >= limit ||
|
|
158
|
+
budget.dirsVisited >= budget.maxDirs ||
|
|
159
|
+
Date.now() >= budget.deadline
|
|
160
|
+
) {
|
|
137
161
|
return;
|
|
138
162
|
}
|
|
163
|
+
// Reserve this directory's slot SYNCHRONOUSLY (before the readdir await) so a
|
|
164
|
+
// concurrent Promise.all fan-out can't blow past the budget: every scheduled
|
|
165
|
+
// sibling sees the updated count before it yields, making the cap effectively hard.
|
|
166
|
+
budget.dirsVisited++;
|
|
139
167
|
|
|
140
168
|
let entries: fs.Dirent[];
|
|
141
169
|
try {
|
|
@@ -157,7 +185,7 @@ async function collectAgentsMdFiles(
|
|
|
157
185
|
}
|
|
158
186
|
}
|
|
159
187
|
|
|
160
|
-
if (depth ===
|
|
188
|
+
if (depth === maxDepth) {
|
|
161
189
|
return;
|
|
162
190
|
}
|
|
163
191
|
|
|
@@ -168,24 +196,47 @@ async function collectAgentsMdFiles(
|
|
|
168
196
|
|
|
169
197
|
await Promise.all(
|
|
170
198
|
childDirs.map(async child => {
|
|
171
|
-
if (discovered.size >= limit) return;
|
|
172
|
-
await collectAgentsMdFiles(root, path.join(dir, child), depth + 1, limit, discovered);
|
|
199
|
+
if (discovered.size >= limit || budget.dirsVisited >= budget.maxDirs || Date.now() >= budget.deadline) return;
|
|
200
|
+
await collectAgentsMdFiles(root, path.join(dir, child), depth + 1, limit, maxDepth, discovered, budget);
|
|
173
201
|
}),
|
|
174
202
|
);
|
|
175
203
|
}
|
|
176
204
|
|
|
177
|
-
|
|
205
|
+
/** Options for {@link discoverAgentsMdFiles} (all bounded by defaults). */
|
|
206
|
+
export interface DiscoverAgentsMdOptions {
|
|
207
|
+
limit?: number;
|
|
208
|
+
maxDepth?: number;
|
|
209
|
+
maxDirs?: number;
|
|
210
|
+
budgetMs?: number;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Walk `root` (bounded by depth, match-limit, directory budget, and a wall-clock
|
|
215
|
+
* deadline) collecting nested `XCSH.md` paths. Returns the sorted matches plus the
|
|
216
|
+
* number of directories visited (for observability/tests). Never throws.
|
|
217
|
+
*/
|
|
218
|
+
export async function discoverAgentsMdFiles(
|
|
219
|
+
root: string,
|
|
220
|
+
opts: DiscoverAgentsMdOptions = {},
|
|
221
|
+
): Promise<{ files: string[]; dirsVisited: number }> {
|
|
222
|
+
const limit = opts.limit ?? AGENTS_MD_LIMIT;
|
|
223
|
+
const maxDepth = opts.maxDepth ?? AGENTS_MD_MAX_DEPTH;
|
|
224
|
+
const budget: WalkBudget = {
|
|
225
|
+
maxDirs: opts.maxDirs ?? AGENTS_MD_MAX_DIRS,
|
|
226
|
+
deadline: Date.now() + (opts.budgetMs ?? AGENTS_MD_WALK_BUDGET_MS),
|
|
227
|
+
dirsVisited: 0,
|
|
228
|
+
};
|
|
178
229
|
try {
|
|
179
230
|
const discovered = new Set<string>();
|
|
180
|
-
await collectAgentsMdFiles(root, root, 0, limit, discovered);
|
|
181
|
-
return Array.from(discovered).sort().slice(0, limit);
|
|
231
|
+
await collectAgentsMdFiles(root, root, 0, limit, maxDepth, discovered, budget);
|
|
232
|
+
return { files: Array.from(discovered).sort().slice(0, limit), dirsVisited: budget.dirsVisited };
|
|
182
233
|
} catch {
|
|
183
|
-
return [];
|
|
234
|
+
return { files: [], dirsVisited: budget.dirsVisited };
|
|
184
235
|
}
|
|
185
236
|
}
|
|
186
237
|
|
|
187
|
-
async function buildAgentsMdSearch(cwd: string): Promise<AgentsMdSearch> {
|
|
188
|
-
const files = await
|
|
238
|
+
export async function buildAgentsMdSearch(cwd: string): Promise<AgentsMdSearch> {
|
|
239
|
+
const { files } = await discoverAgentsMdFiles(cwd);
|
|
189
240
|
return {
|
|
190
241
|
scopePath: ".",
|
|
191
242
|
limit: AGENTS_MD_LIMIT,
|
|
@@ -449,6 +500,9 @@ export interface BuildSystemPromptOptions {
|
|
|
449
500
|
cwd?: string;
|
|
450
501
|
/** Pre-loaded context files (skips discovery if provided). */
|
|
451
502
|
contextFiles?: Array<{ path: string; content: string; depth?: number }>;
|
|
503
|
+
/** Pre-computed nested-XCSH.md search (skips the CWD walk if provided). Hoisted
|
|
504
|
+
* once per session so tool-refresh rebuilds never re-walk the tree. */
|
|
505
|
+
agentsMdSearch?: AgentsMdSearch;
|
|
452
506
|
/**
|
|
453
507
|
* Explicit disabled extension IDs applied to context-file discovery instead of the
|
|
454
508
|
* global settings default. Pass `[]` to discover independent of process-wide settings.
|
|
@@ -519,6 +573,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
519
573
|
toolNames: providedToolNames,
|
|
520
574
|
cwd,
|
|
521
575
|
contextFiles: providedContextFiles,
|
|
576
|
+
agentsMdSearch: providedAgentsMdSearch,
|
|
522
577
|
skills: providedSkills,
|
|
523
578
|
rules,
|
|
524
579
|
alwaysApplyRules,
|
|
@@ -541,7 +596,9 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
541
596
|
cwd: resolvedCwd,
|
|
542
597
|
disabledExtensions: options.disabledExtensions,
|
|
543
598
|
});
|
|
544
|
-
const agentsMdSearchPromise =
|
|
599
|
+
const agentsMdSearchPromise = providedAgentsMdSearch
|
|
600
|
+
? Promise.resolve(providedAgentsMdSearch)
|
|
601
|
+
: logger.time("buildAgentsMdSearch", buildAgentsMdSearch, resolvedCwd);
|
|
545
602
|
const mergedSkillsSettings = {
|
|
546
603
|
...skillsSettings,
|
|
547
604
|
customDirectories: [...(skillsSettings?.customDirectories ?? []), ...(options.contextSkillDirs ?? [])],
|