@f5-sales-demo/xcsh 20.0.3 → 20.0.4
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 +6 -0
- package/package.json +8 -8
- package/src/exec/bash-executor.ts +1 -1
- package/src/internal-urls/build-info.generated.ts +8 -8
- package/src/internal-urls/fleet-resolve.ts +3 -6
- package/src/modes/components/status-line.ts +1 -1
- package/src/prompts/tools/bash.md +2 -0
- package/src/sdk.ts +2 -2
- package/src/tools/bash.ts +9 -20
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [20.0.4] - 2026-08-01
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Reset model bash tool calls to the session working directory so a command-local `cd` cannot silently relocate later calls ([#2724](https://github.com/f5-sales-demo/xcsh/issues/2724))
|
|
10
|
+
|
|
5
11
|
## [20.0.3] - 2026-08-01
|
|
6
12
|
|
|
7
13
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5-sales-demo/xcsh",
|
|
4
|
-
"version": "20.0.
|
|
4
|
+
"version": "20.0.4",
|
|
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",
|
|
@@ -57,13 +57,13 @@
|
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@agentclientprotocol/sdk": "1.3.0",
|
|
59
59
|
"@mozilla/readability": "^0.6",
|
|
60
|
-
"@f5-sales-demo/xcsh-stats": "20.0.
|
|
61
|
-
"@f5-sales-demo/pi-agent-core": "20.0.
|
|
62
|
-
"@f5-sales-demo/pi-ai": "20.0.
|
|
63
|
-
"@f5-sales-demo/pi-natives": "20.0.
|
|
64
|
-
"@f5-sales-demo/pi-resource-management": "20.0.
|
|
65
|
-
"@f5-sales-demo/pi-tui": "20.0.
|
|
66
|
-
"@f5-sales-demo/pi-utils": "20.0.
|
|
60
|
+
"@f5-sales-demo/xcsh-stats": "20.0.4",
|
|
61
|
+
"@f5-sales-demo/pi-agent-core": "20.0.4",
|
|
62
|
+
"@f5-sales-demo/pi-ai": "20.0.4",
|
|
63
|
+
"@f5-sales-demo/pi-natives": "20.0.4",
|
|
64
|
+
"@f5-sales-demo/pi-resource-management": "20.0.4",
|
|
65
|
+
"@f5-sales-demo/pi-tui": "20.0.4",
|
|
66
|
+
"@f5-sales-demo/pi-utils": "20.0.4",
|
|
67
67
|
"@sinclair/typebox": "^0.34",
|
|
68
68
|
"@xterm/headless": "^6.0",
|
|
69
69
|
"ajv": "^8.20",
|
|
@@ -64,7 +64,7 @@ export interface BashResult {
|
|
|
64
64
|
outputLines: number;
|
|
65
65
|
outputBytes: number;
|
|
66
66
|
artifactId?: string;
|
|
67
|
-
/** Actual working directory after the command ran
|
|
67
|
+
/** Actual working directory after the command ran; direct operator-shell callers may persist it. */
|
|
68
68
|
newCwd?: string;
|
|
69
69
|
}
|
|
70
70
|
|
|
@@ -17,17 +17,17 @@ export interface BuildInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BUILD_INFO: BuildInfo = {
|
|
20
|
-
"version": "20.0.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "20.0.4",
|
|
21
|
+
"commit": "c1606427884c61f77f5f1fbf1c4ab7511920061b",
|
|
22
|
+
"shortCommit": "c160642",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v20.0.
|
|
25
|
-
"commitDate": "2026-08-
|
|
26
|
-
"buildDate": "2026-08-
|
|
24
|
+
"tag": "v20.0.4",
|
|
25
|
+
"commitDate": "2026-08-01T06:54:53Z",
|
|
26
|
+
"buildDate": "2026-08-01T07:20:58.905Z",
|
|
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/v20.0.
|
|
31
|
+
"commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/c1606427884c61f77f5f1fbf1c4ab7511920061b",
|
|
32
|
+
"releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v20.0.4"
|
|
33
33
|
};
|
|
@@ -149,13 +149,10 @@ export interface CwdEventSource {
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
/**
|
|
152
|
-
* A getter for the working directory the *session* is in, not the one the process
|
|
153
|
-
* started in.
|
|
152
|
+
* A getter for the working directory the *session* is rooted in, not the one the process started in.
|
|
154
153
|
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* `process.cwd()` would therefore keep reporting the startup repository, so a
|
|
158
|
-
* `content` grant could survive after moving into a `developer` repository.
|
|
154
|
+
* Model bash calls reset to this root, so a command-local `cd` must not emit this event. The event is
|
|
155
|
+
* reserved for an explicit session relocation; `process.chdir` is never used for either case.
|
|
159
156
|
*/
|
|
160
157
|
export function createLiveCwdGetter(initialCwd: string, events?: CwdEventSource): () => string {
|
|
161
158
|
let current = initialCwd;
|
|
@@ -212,7 +212,7 @@ export class StatusLineComponent implements Component {
|
|
|
212
212
|
this.#invalidateGitCaches();
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
/** Update the displayed working directory
|
|
215
|
+
/** Update the displayed working directory after an operator command or explicit session move. */
|
|
216
216
|
setCwd(cwd: string): void {
|
|
217
217
|
this.#cwd = cwd;
|
|
218
218
|
this.#invalidateGitCaches();
|
|
@@ -2,6 +2,8 @@ Executes bash command in shell session for terminal operations like git, bun, ca
|
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
4
|
- You **MUST** use `cwd` parameter to set working directory instead of `cd dir && …`
|
|
5
|
+
- Every call starts in the session working directory, or in `cwd` when provided. A `cd` affects only
|
|
6
|
+
that call and does not relocate later tool calls.
|
|
5
7
|
- Always provide a `description` parameter: a short, human-readable summary of what the command does (e.g. "Install dependencies", "Run test suite"). This is displayed in compact mode and during execution.
|
|
6
8
|
- Prefer `env: { NAME: "…" }` for multiline, quote-heavy, or untrusted values instead of inlining them into shell syntax; reference them from the command as `$NAME`
|
|
7
9
|
- Quote variable expansions like `"$NAME"` to preserve exact content and avoid shell parsing bugs
|
package/src/sdk.ts
CHANGED
|
@@ -1170,8 +1170,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1170
1170
|
},
|
|
1171
1171
|
}),
|
|
1172
1172
|
getPluginRoots: () => listXcshPluginRoots(os.homedir(), cwd).then(r => r.roots),
|
|
1173
|
-
// Classification
|
|
1174
|
-
//
|
|
1173
|
+
// Classification follows explicit session relocation events, not command-local `cd` and not the
|
|
1174
|
+
// process cwd. Model bash calls reset to this session root on every invocation (#2724).
|
|
1175
1175
|
fleetDeps: { cwd: createLiveCwdGetter(cwd, eventBus) },
|
|
1176
1176
|
}),
|
|
1177
1177
|
);
|
package/src/tools/bash.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
} from "@f5-sales-demo/pi-agent-core";
|
|
8
8
|
import type { Component } from "@f5-sales-demo/pi-tui";
|
|
9
9
|
import { ImageProtocol, TERMINAL, Text } from "@f5-sales-demo/pi-tui";
|
|
10
|
-
import { $env, getProjectDir, isEnoent, prompt
|
|
10
|
+
import { $env, getProjectDir, isEnoent, prompt } from "@f5-sales-demo/pi-utils";
|
|
11
11
|
import { Type } from "@sinclair/typebox";
|
|
12
12
|
import { Settings } from "../config/settings";
|
|
13
13
|
import { type BashResult, executeBash } from "../exec/bash-executor";
|
|
@@ -516,17 +516,13 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
516
516
|
/**
|
|
517
517
|
* Where this session's boundary is anchored.
|
|
518
518
|
*
|
|
519
|
-
* **Not
|
|
520
|
-
*
|
|
521
|
-
*
|
|
522
|
-
*
|
|
523
|
-
* (correctly — `/usr` is not sensitive), and the *next* fence was rooted at `/usr`, where the parent
|
|
524
|
-
* deny of `/work` cannot be expressed because `dirname("/usr")` is `/` and denying the root is refused
|
|
525
|
-
* as too broad. `/work/custB` then became readable **and** writable. Two tool calls, no exotic
|
|
526
|
-
* spelling, and the tenant boundary was gone.
|
|
519
|
+
* **Not a command's final PWD.** Every model tool call starts from `session.cwd` and a command-local
|
|
520
|
+
* `cd` is discarded when that call ends (#2724). Before that reset existed, writing the final PWD
|
|
521
|
+
* back into `session.cwd` also moved this fence: after `cd /usr`, the next boundary was rooted at
|
|
522
|
+
* `/usr` and could no longer protect a workspace under `/work` (#2589).
|
|
527
523
|
*
|
|
528
|
-
*
|
|
529
|
-
* object rather than held on this instance because the tool is built by a factory
|
|
524
|
+
* The anchor remains captured once per session as a defensive invariant and never follows the shell.
|
|
525
|
+
* It is keyed on the session object rather than held on this instance because the tool is built by a factory
|
|
530
526
|
* (`bash: s => new BashTool(s)`) and must not depend on how long an instance happens to live.
|
|
531
527
|
*
|
|
532
528
|
* An *operator* moving the project — startup, or the slash command that calls `setProjectDir` — does
|
|
@@ -546,8 +542,8 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
546
542
|
return this.session.cwd;
|
|
547
543
|
}
|
|
548
544
|
if (anchor.project !== project) {
|
|
549
|
-
// The operator moved the project.
|
|
550
|
-
//
|
|
545
|
+
// The operator moved the project. This is an explicit boundary relocation, unlike a model's
|
|
546
|
+
// command-local `cd`.
|
|
551
547
|
FENCE_ANCHORS.set(this.session, { root: project, project });
|
|
552
548
|
return project;
|
|
553
549
|
}
|
|
@@ -787,13 +783,6 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
787
783
|
}
|
|
788
784
|
},
|
|
789
785
|
});
|
|
790
|
-
// Update working directory if the persistent shell changed it
|
|
791
|
-
if ("newCwd" in result && result.newCwd && result.newCwd !== this.session.cwd) {
|
|
792
|
-
this.session.cwd = result.newCwd;
|
|
793
|
-
setShellPwd(result.newCwd);
|
|
794
|
-
this.session.eventBus?.emit("cwd:changed", result.newCwd);
|
|
795
|
-
}
|
|
796
|
-
|
|
797
786
|
if (result.cancelled) {
|
|
798
787
|
if (signal?.aborted) {
|
|
799
788
|
throw new ToolAbortError(normalizeResultOutput(result) || "Command aborted");
|