@f5-sales-demo/xcsh 19.90.1 → 19.91.1
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 +14 -14
- package/src/browser/chat-handler.ts +56 -0
- package/src/extensibility/extensions/bundled/herdr-reporter.ts +112 -48
- package/src/internal-urls/build-info.generated.ts +8 -8
- package/src/internal-urls/console-catalog.generated.ts +2 -2
- package/src/modes/acp/acp-agent.ts +12 -48
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.91.1",
|
|
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",
|
|
@@ -54,28 +54,28 @@
|
|
|
54
54
|
"check-workflow-field-coverage": "bun scripts/check-workflow-field-coverage.ts"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@agentclientprotocol/sdk": "
|
|
57
|
+
"@agentclientprotocol/sdk": "1.3.0",
|
|
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.91.1",
|
|
60
|
+
"@f5-sales-demo/pi-agent-core": "19.91.1",
|
|
61
|
+
"@f5-sales-demo/pi-ai": "19.91.1",
|
|
62
|
+
"@f5-sales-demo/pi-natives": "19.91.1",
|
|
63
|
+
"@f5-sales-demo/pi-resource-management": "19.91.1",
|
|
64
|
+
"@f5-sales-demo/pi-tui": "19.91.1",
|
|
65
|
+
"@f5-sales-demo/pi-utils": "19.91.1",
|
|
66
66
|
"@sinclair/typebox": "^0.34",
|
|
67
67
|
"@xterm/headless": "^6.0",
|
|
68
68
|
"ajv": "^8.20",
|
|
69
69
|
"chalk": "^5.6",
|
|
70
|
-
"diff": "^
|
|
71
|
-
"fflate": "0.8.
|
|
70
|
+
"diff": "^9.0",
|
|
71
|
+
"fflate": "0.8.3",
|
|
72
72
|
"handlebars": "^4.7",
|
|
73
73
|
"linkedom": "^0.18",
|
|
74
|
-
"lru-cache": "11.
|
|
75
|
-
"markit-ai": "0.5.
|
|
74
|
+
"lru-cache": "11.5.2",
|
|
75
|
+
"markit-ai": "0.5.3",
|
|
76
76
|
"puppeteer": "^24.37",
|
|
77
77
|
"yaml": "2.9.0",
|
|
78
|
-
"zod": "4.3
|
|
78
|
+
"zod": "4.4.3"
|
|
79
79
|
},
|
|
80
80
|
"devDependencies": {
|
|
81
81
|
"@types/bun": "^1.3"
|
|
@@ -275,6 +275,32 @@ export class ChatHandler {
|
|
|
275
275
|
chat.lastKeepaliveAt = nowMs;
|
|
276
276
|
this.#server.send({ type: "chat_keepalive", id: chat.id } satisfies ChatKeepalive);
|
|
277
277
|
}
|
|
278
|
+
} else if (ame.type === "server_tool_start") {
|
|
279
|
+
// PROVIDER-SIDE ACTIVITY (#2340): the provider runs web search itself, which takes
|
|
280
|
+
// seconds before any token streams. This notice is the only feedback in that window —
|
|
281
|
+
// without it the panel shows a bare "Thinking…" and looks hung. It doubles as a
|
|
282
|
+
// liveness signal, since the panel re-arms its first-token timer on a tool notice.
|
|
283
|
+
const label = serverToolLabels(ame.toolName);
|
|
284
|
+
this.#server.send({
|
|
285
|
+
type: "chat_tool_notice",
|
|
286
|
+
id: chat.id,
|
|
287
|
+
tool: ame.toolName,
|
|
288
|
+
ok: true,
|
|
289
|
+
detail: ame.query ? `${label.running}: ${ame.query}` : `${label.running}…`,
|
|
290
|
+
});
|
|
291
|
+
} else if (ame.type === "server_tool_end") {
|
|
292
|
+
const label = serverToolLabels(ame.toolName);
|
|
293
|
+
const failed = ame.errorCode !== undefined;
|
|
294
|
+
const count = ame.resultCount ?? 0;
|
|
295
|
+
this.#server.send({
|
|
296
|
+
type: "chat_tool_notice",
|
|
297
|
+
id: chat.id,
|
|
298
|
+
tool: ame.toolName,
|
|
299
|
+
ok: !failed,
|
|
300
|
+
detail: failed
|
|
301
|
+
? `${label.done} failed: ${ame.errorCode}`
|
|
302
|
+
: `${label.done}: ${count} result${count === 1 ? "" : "s"}`,
|
|
303
|
+
});
|
|
278
304
|
}
|
|
279
305
|
} else if (event.type === "message_end" && event.message.role === "assistant") {
|
|
280
306
|
const msg = event.message as AssistantMessage;
|
|
@@ -686,10 +712,40 @@ function trimTrailingMarkup(url: string): string {
|
|
|
686
712
|
return url.replace(/[*_~`,.;:!?'")\]}>]+$/, "");
|
|
687
713
|
}
|
|
688
714
|
|
|
715
|
+
/** Panel-facing labels for provider-side tools, used for the activity rows (#2340). */
|
|
716
|
+
function serverToolLabels(toolName: string): { running: string; done: string } {
|
|
717
|
+
switch (toolName) {
|
|
718
|
+
case "web_search":
|
|
719
|
+
return { running: "Searching the web", done: "Web search" };
|
|
720
|
+
case "web_fetch":
|
|
721
|
+
return { running: "Fetching a page", done: "Page fetch" };
|
|
722
|
+
default:
|
|
723
|
+
return { running: `${toolName}: running`, done: toolName };
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
689
727
|
export function extractReferences(msg: AssistantMessage): ChatReference[] {
|
|
690
728
|
const refs: ChatReference[] = [];
|
|
691
729
|
const seen = new Set<string>();
|
|
692
730
|
|
|
731
|
+
// STRUCTURED CITATIONS FIRST (#2340): a provider-side search reports the real page title, so
|
|
732
|
+
// it beats the regex scrape below — which can only guess a title from the URL path, and finds
|
|
733
|
+
// nothing at all when the model cites a source without printing its URL in the prose. Done as
|
|
734
|
+
// its own pass so a citation in a later block still wins over a scrape in an earlier one.
|
|
735
|
+
for (const block of msg.content) {
|
|
736
|
+
if (block.type !== "text" || !block.citations) continue;
|
|
737
|
+
for (const citation of block.citations) {
|
|
738
|
+
const url = citation.url;
|
|
739
|
+
if (!url || seen.has(url)) continue;
|
|
740
|
+
seen.add(url);
|
|
741
|
+
refs.push({
|
|
742
|
+
kind: classifyReferenceKind(url),
|
|
743
|
+
title: citation.title?.trim() || titleFromUrl(url),
|
|
744
|
+
url,
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
693
749
|
for (const block of msg.content) {
|
|
694
750
|
if (block.type !== "text") continue;
|
|
695
751
|
|
|
@@ -29,6 +29,24 @@ import type { ExtensionAPI, ExtensionContext } from "@f5-sales-demo/xcsh";
|
|
|
29
29
|
* The extension is completely inert unless it is running inside a herdr pane
|
|
30
30
|
* (detected via `HERDR_PANE_ID`), so it has zero effect for users who do not run
|
|
31
31
|
* xcsh under herdr.
|
|
32
|
+
*
|
|
33
|
+
* Sequencing contract with herdr — do not regress. herdr keeps one monotonic
|
|
34
|
+
* `seq` per *source*, shared across every method, and silently discards any frame
|
|
35
|
+
* whose `seq` is not greater than the last it accepted for `herdr:xcsh`. Two
|
|
36
|
+
* consequences drive the design below, and both previously cost panes their
|
|
37
|
+
* resume reference:
|
|
38
|
+
*
|
|
39
|
+
* 1. `seq` is seeded from the wall clock, not 0, so a restarted xcsh always
|
|
40
|
+
* outranks its predecessor in the same pane. With a per-process counter from
|
|
41
|
+
* 0, every frame from the second xcsh in a pane looks stale and is dropped.
|
|
42
|
+
* 2. Frames go out one at a time through `enqueue`. They used to be
|
|
43
|
+
* fire-and-forget on independent sockets, so ordering was left to chance.
|
|
44
|
+
* 3. The state frame is always sent *before* the session frame. herdr discards a
|
|
45
|
+
* `pane.report_agent_session` for a pane whose agent it does not yet own, so
|
|
46
|
+
* the state frame has to establish `herdr:xcsh` as the pane's agent first.
|
|
47
|
+
* Verified against a live herdr: session-then-state (even with a correctly
|
|
48
|
+
* ascending `seq`) leaves `agent_session` null, while state-then-session
|
|
49
|
+
* records it.
|
|
32
50
|
*/
|
|
33
51
|
|
|
34
52
|
const HERDR_AGENT_LABEL = "xcsh";
|
|
@@ -42,26 +60,43 @@ const SOCKET_TIMEOUT_MS = 2000;
|
|
|
42
60
|
|
|
43
61
|
/**
|
|
44
62
|
* Write one newline-delimited JSON-RPC request to herdr's unix socket and close.
|
|
45
|
-
*
|
|
46
|
-
*
|
|
63
|
+
* The response is ignored and every failure path is reported via `onError`
|
|
64
|
+
* without throwing, so a dead socket never disturbs the agent.
|
|
65
|
+
*
|
|
66
|
+
* Resolves once the frame has been flushed (or the attempt failed or timed out),
|
|
67
|
+
* which is what lets callers order frames relative to one another.
|
|
47
68
|
*/
|
|
48
69
|
function sendToHerdrSocket(
|
|
49
70
|
socketPath: string,
|
|
50
71
|
method: string,
|
|
51
72
|
params: Record<string, unknown>,
|
|
52
73
|
onError: (err: unknown) => void,
|
|
53
|
-
): void {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
74
|
+
): Promise<void> {
|
|
75
|
+
return new Promise<void>(resolve => {
|
|
76
|
+
let settled = false;
|
|
77
|
+
const settle = (): void => {
|
|
78
|
+
if (settled) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
settled = true;
|
|
82
|
+
resolve();
|
|
83
|
+
};
|
|
84
|
+
const conn = connect({ path: socketPath });
|
|
85
|
+
// Never let a report keep xcsh's event loop alive.
|
|
86
|
+
conn.unref();
|
|
87
|
+
conn.once("error", err => {
|
|
88
|
+
onError(err);
|
|
89
|
+
conn.destroy();
|
|
90
|
+
settle();
|
|
91
|
+
});
|
|
92
|
+
conn.once("connect", () => {
|
|
93
|
+
conn.end(`${JSON.stringify({ id: "xcsh:herdr-reporter", method, params })}\n`, settle);
|
|
94
|
+
});
|
|
95
|
+
conn.setTimeout(SOCKET_TIMEOUT_MS, () => {
|
|
96
|
+
conn.destroy();
|
|
97
|
+
settle();
|
|
98
|
+
});
|
|
63
99
|
});
|
|
64
|
-
conn.setTimeout(SOCKET_TIMEOUT_MS, () => conn.destroy());
|
|
65
100
|
}
|
|
66
101
|
|
|
67
102
|
/** Translate a JSON-RPC report/release into `herdr` CLI arguments (fallback). */
|
|
@@ -90,10 +125,19 @@ export default function herdrReporter(pi: ExtensionAPI): void {
|
|
|
90
125
|
return;
|
|
91
126
|
}
|
|
92
127
|
|
|
93
|
-
|
|
128
|
+
// Seeded from the wall clock at microsecond scale rather than 0 so a new xcsh
|
|
129
|
+
// process in a pane always starts above whatever the previous process reached.
|
|
130
|
+
// Matches the pi/omp reporters and stays well inside Number.MAX_SAFE_INTEGER.
|
|
131
|
+
let seq = Date.now() * 1000;
|
|
94
132
|
// Tracks whether an interactive prompt is currently awaiting the user, so an
|
|
95
133
|
// agent_end that fires while a prompt is open is reported as blocked, not idle.
|
|
96
134
|
let promptOpen = false;
|
|
135
|
+
// Last session ref already delivered, so repeated lifecycle events do not
|
|
136
|
+
// re-send an unchanged ref every turn.
|
|
137
|
+
let lastSessionRefKey: string | undefined;
|
|
138
|
+
// Serializes frames: herdr compares `seq` across all methods for this source,
|
|
139
|
+
// so frames must reach it in the order they were generated.
|
|
140
|
+
let queue: Promise<void> = Promise.resolve();
|
|
97
141
|
|
|
98
142
|
pi.setLabel(HERDR_AGENT_LABEL);
|
|
99
143
|
|
|
@@ -103,17 +147,27 @@ export default function herdrReporter(pi: ExtensionAPI): void {
|
|
|
103
147
|
});
|
|
104
148
|
};
|
|
105
149
|
|
|
106
|
-
|
|
150
|
+
/** Chain a frame onto the tail of the send queue. Never rejects. */
|
|
151
|
+
const enqueue = (task: () => Promise<void>): Promise<void> => {
|
|
152
|
+
queue = queue.then(task, task).catch(onError);
|
|
153
|
+
return queue;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const send = (method: string, params: Record<string, unknown>): Promise<void> => {
|
|
107
157
|
const socketPath = process.env.HERDR_SOCKET_PATH;
|
|
108
158
|
if (socketPath) {
|
|
109
|
-
sendToHerdrSocket(socketPath, method, params, onError);
|
|
110
|
-
return;
|
|
159
|
+
return enqueue(() => sendToHerdrSocket(socketPath, method, params, onError));
|
|
111
160
|
}
|
|
112
161
|
// Fallback for the rare case herdr did not inject a socket path.
|
|
113
|
-
|
|
162
|
+
return enqueue(() =>
|
|
163
|
+
pi
|
|
164
|
+
.exec("herdr", toCliArgs(method, params))
|
|
165
|
+
.then(() => undefined)
|
|
166
|
+
.catch(onError),
|
|
167
|
+
);
|
|
114
168
|
};
|
|
115
169
|
|
|
116
|
-
const report = (state: "idle" | "working" | "blocked"): void =>
|
|
170
|
+
const report = (state: "idle" | "working" | "blocked"): Promise<void> =>
|
|
117
171
|
send(REPORT_METHOD, {
|
|
118
172
|
pane_id: paneId,
|
|
119
173
|
source: HERDR_SOURCE,
|
|
@@ -121,7 +175,6 @@ export default function herdrReporter(pi: ExtensionAPI): void {
|
|
|
121
175
|
state,
|
|
122
176
|
seq: seq++,
|
|
123
177
|
});
|
|
124
|
-
};
|
|
125
178
|
|
|
126
179
|
// Report the current session's identity so herdr can resume this pane
|
|
127
180
|
// (`xcsh --resume=<session>`) after a server restart. This is sent only over
|
|
@@ -129,10 +182,16 @@ export default function herdrReporter(pi: ExtensionAPI): void {
|
|
|
129
182
|
// reports via the CLI fallback). Prefer the absolute session file path, which
|
|
130
183
|
// herdr resumes directly; fall back to the session id for non-persisted
|
|
131
184
|
// sessions (e.g. print/RPC mode, where getSessionFile() is undefined).
|
|
132
|
-
|
|
185
|
+
//
|
|
186
|
+
// Called from every lifecycle handler because xcsh creates the session file
|
|
187
|
+
// lazily: getSessionFile() can still be undefined at session_start and even at
|
|
188
|
+
// agent_start, and a ref missed there would otherwise be lost until the next
|
|
189
|
+
// turn — long enough for a restart to lose the pane. Unchanged refs are
|
|
190
|
+
// suppressed, so the extra call sites cost nothing on the wire.
|
|
191
|
+
const reportSession = (ctx: ExtensionContext): Promise<void> => {
|
|
133
192
|
const socketPath = process.env.HERDR_SOCKET_PATH;
|
|
134
193
|
if (!socketPath) {
|
|
135
|
-
return;
|
|
194
|
+
return Promise.resolve();
|
|
136
195
|
}
|
|
137
196
|
let sessionRef: Record<string, unknown> | undefined;
|
|
138
197
|
try {
|
|
@@ -147,58 +206,63 @@ export default function herdrReporter(pi: ExtensionAPI): void {
|
|
|
147
206
|
}
|
|
148
207
|
} catch (err) {
|
|
149
208
|
onError(err);
|
|
150
|
-
return;
|
|
209
|
+
return Promise.resolve();
|
|
151
210
|
}
|
|
152
211
|
if (!sessionRef) {
|
|
153
|
-
return;
|
|
212
|
+
return Promise.resolve();
|
|
154
213
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
214
|
+
const refKey = JSON.stringify(sessionRef);
|
|
215
|
+
if (refKey === lastSessionRefKey) {
|
|
216
|
+
return Promise.resolve();
|
|
217
|
+
}
|
|
218
|
+
lastSessionRefKey = refKey;
|
|
219
|
+
const frame = {
|
|
220
|
+
pane_id: paneId,
|
|
221
|
+
source: HERDR_SOURCE,
|
|
222
|
+
agent: HERDR_AGENT_LABEL,
|
|
223
|
+
seq: seq++,
|
|
224
|
+
...sessionRef,
|
|
225
|
+
};
|
|
226
|
+
return enqueue(() => sendToHerdrSocket(socketPath, SESSION_METHOD, frame, onError));
|
|
167
227
|
};
|
|
168
228
|
|
|
169
229
|
// Announce presence and session identity as soon as the session is initialized.
|
|
170
|
-
pi.on("session_start", (_event, ctx) => {
|
|
171
|
-
|
|
172
|
-
|
|
230
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
231
|
+
await report("idle");
|
|
232
|
+
await reportSession(ctx);
|
|
173
233
|
});
|
|
174
234
|
|
|
175
235
|
// Busy while the agent loop is streaming a response. Re-report session identity
|
|
176
236
|
// in case the active session file changed (e.g. after /new, /resume, or /fork).
|
|
177
|
-
pi.on("agent_start", (_event, ctx) => {
|
|
178
|
-
|
|
179
|
-
|
|
237
|
+
pi.on("agent_start", async (_event, ctx) => {
|
|
238
|
+
await report("working");
|
|
239
|
+
await reportSession(ctx);
|
|
180
240
|
});
|
|
181
241
|
|
|
182
242
|
// Back to idle when the loop ends — unless we are waiting on a user prompt.
|
|
183
|
-
|
|
184
|
-
|
|
243
|
+
// This is also the first point where a lazily-created session file is certain
|
|
244
|
+
// to exist, so it is the backstop for capturing the resume ref.
|
|
245
|
+
pi.on("agent_end", async (_event, ctx) => {
|
|
246
|
+
await report(promptOpen ? "blocked" : "idle");
|
|
247
|
+
await reportSession(ctx);
|
|
185
248
|
});
|
|
186
249
|
|
|
187
250
|
// An interactive prompt (permission gate, ask tool, confirm/input) is
|
|
188
251
|
// awaiting the user: that is herdr's "needs attention" (blocked) state.
|
|
189
252
|
pi.on("user_prompt_start", () => {
|
|
190
253
|
promptOpen = true;
|
|
191
|
-
report("blocked");
|
|
254
|
+
void report("blocked");
|
|
192
255
|
});
|
|
193
256
|
|
|
194
|
-
pi.on("user_prompt_end", (_event, ctx) => {
|
|
257
|
+
pi.on("user_prompt_end", async (_event, ctx) => {
|
|
195
258
|
promptOpen = false;
|
|
196
|
-
report(ctx.isIdle() ? "idle" : "working");
|
|
259
|
+
await report(ctx.isIdle() ? "idle" : "working");
|
|
260
|
+
await reportSession(ctx);
|
|
197
261
|
});
|
|
198
262
|
|
|
199
263
|
// Relinquish pane authority so herdr stops showing xcsh once we exit.
|
|
200
264
|
pi.on("session_shutdown", () => {
|
|
201
|
-
send(RELEASE_METHOD, {
|
|
265
|
+
void send(RELEASE_METHOD, {
|
|
202
266
|
pane_id: paneId,
|
|
203
267
|
source: HERDR_SOURCE,
|
|
204
268
|
agent: HERDR_AGENT_LABEL,
|
|
@@ -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.91.1",
|
|
21
|
+
"commit": "cb975aa015ff199ecdc38c20544471ebf32e3737",
|
|
22
|
+
"shortCommit": "cb975aa",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v19.
|
|
25
|
-
"commitDate": "2026-07-
|
|
26
|
-
"buildDate": "2026-07-
|
|
24
|
+
"tag": "v19.91.1",
|
|
25
|
+
"commitDate": "2026-07-25T17:10:56Z",
|
|
26
|
+
"buildDate": "2026-07-25T17:31:09.895Z",
|
|
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/cb975aa015ff199ecdc38c20544471ebf32e3737",
|
|
32
|
+
"releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v19.91.1"
|
|
33
33
|
};
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import type { ConsoleCatalogData } from "./console-catalog-types";
|
|
4
4
|
|
|
5
|
-
export const CONSOLE_CATALOG_VERSION = "
|
|
5
|
+
export const CONSOLE_CATALOG_VERSION = "e58701e87983ff5967d0d4f68e74a78c64fdfbf1";
|
|
6
6
|
|
|
7
7
|
export const CONSOLE_CATALOG_DATA: ConsoleCatalogData = {
|
|
8
|
-
version: "
|
|
8
|
+
version: "e58701e87983ff5967d0d4f68e74a78c64fdfbf1",
|
|
9
9
|
workflows: {
|
|
10
10
|
"address-allocator/create":
|
|
11
11
|
'schema: urn:xcsh:console:workflow:v1\nid: address-allocator-create\nlabel: Create IP Address Allocators\nresource: address-allocator\noperation: create\npreconditions:\n - user_logged_in\n - namespace_selected\n - "role_minimum: admin"\nparams:\n namespace:\n required: true\n description: Target namespace\n example: demo\n name:\n required: true\n description: IP Address Allocators name (lowercase alphanumeric and hyphens)\n example: example-address-allocator\n address_allocator_mode:\n required: true\n description: Address Allocator Mode\n allocation_unit:\n required: false\n description: Allocation Unit\n default: 0\n address_pool:\n required: false\n description: Address Pool\n default: value\n address_allocation_scheme:\n required: false\n description: "Server-required: Field should be not nil"\n default: value\nsteps:\n - id: navigate-to-list\n action: navigate\n url: /web/workspaces/multi-cloud-network-connect/manage/networking/legacy_network_configuration/address_allocators\n wait_for: text(\'IP Address Allocators\')\n description: Navigate to IP Address Allocators list page\n - id: click-add-tab\n action: click\n selector: text(\'Add IP Address Allocator\')\n wait_for: textbox[name=\'Name\']\n description: Click Add IP Address Allocator to open the create form\n - id: fill-name\n action: fill\n selector: textbox[name=\'Name\']\n value: "{name}"\n description: Enter Name\n - id: select-address_allocator_mode\n action: select\n selector: listbox\n context: Address Allocator Mode section\n value: "{address_allocator_mode}"\n description: Select Address Allocator Mode\n - id: fill-allocation_unit\n action: fill\n selector: spinbutton[name=\'Allocation Unit\']\n value: "{allocation_unit}"\n description: Set Allocation Unit\n - id: fill-address_pool\n action: fill\n selector: ngx-datatable input.form-control\n context: Address Pool table\n value: "{address_pool}"\n description: Enter Address Pool in the existing table row (no Add Item needed — the table ships one empty row)\n - id: select-address_allocation_scheme\n action: select\n selector: listbox\n context: Address Allocation Scheme section\n value: "{address_allocation_scheme}"\n description: Select Address Allocation Scheme\n - id: save\n action: click\n selector: "[class*=\'save-bt\'],[class*=\'submit-button\']"\n context: footer\n wait_for: text(\'{name}\')\n wait_timeout_ms: 30000\n description: Save/submit the form (union selector matches save-bt OR submit-button)\npostconditions:\n - resource_list_page_visible\n - "resource_name_in_list: {name}"\nmetadata:\n confidence: inferred\n discovered_at: 2026-06-24\n console_version: "2025.06"\n notes: Auto-generated by scripts/generate-workflows.ts from api-specs-enriched field metadata.\n',
|
|
@@ -25,14 +25,11 @@ import {
|
|
|
25
25
|
type ResumeSessionResponse,
|
|
26
26
|
type SessionConfigOption,
|
|
27
27
|
type SessionInfo,
|
|
28
|
-
type SessionModelState,
|
|
29
28
|
type SessionModeState,
|
|
30
29
|
type SessionNotification,
|
|
31
30
|
type SessionUpdate,
|
|
32
31
|
type SetSessionConfigOptionRequest,
|
|
33
32
|
type SetSessionConfigOptionResponse,
|
|
34
|
-
type SetSessionModelRequest,
|
|
35
|
-
type SetSessionModelResponse,
|
|
36
33
|
type SetSessionModeRequest,
|
|
37
34
|
type SetSessionModeResponse,
|
|
38
35
|
type Usage,
|
|
@@ -201,7 +198,6 @@ export class AcpAgent implements Agent {
|
|
|
201
198
|
const response: NewSessionResponse = {
|
|
202
199
|
sessionId: record.session.sessionId,
|
|
203
200
|
configOptions: this.#buildConfigOptions(record.session),
|
|
204
|
-
models: this.#buildModelState(record.session),
|
|
205
201
|
modes: this.#buildModeState(),
|
|
206
202
|
};
|
|
207
203
|
this.#scheduleBootstrapUpdates(record.session.sessionId);
|
|
@@ -214,7 +210,6 @@ export class AcpAgent implements Agent {
|
|
|
214
210
|
await this.#replaySessionHistory(record);
|
|
215
211
|
const response: LoadSessionResponse = {
|
|
216
212
|
configOptions: this.#buildConfigOptions(record.session),
|
|
217
|
-
models: this.#buildModelState(record.session),
|
|
218
213
|
modes: this.#buildModeState(),
|
|
219
214
|
};
|
|
220
215
|
this.#scheduleBootstrapUpdates(record.session.sessionId);
|
|
@@ -238,12 +233,11 @@ export class AcpAgent implements Agent {
|
|
|
238
233
|
};
|
|
239
234
|
}
|
|
240
235
|
|
|
241
|
-
async
|
|
236
|
+
async resumeSession(params: ResumeSessionRequest): Promise<ResumeSessionResponse> {
|
|
242
237
|
this.#assertAbsoluteCwd(params.cwd);
|
|
243
238
|
const record = await this.#resumeManagedSession(params.sessionId, params.cwd, params.mcpServers ?? []);
|
|
244
239
|
const response: ResumeSessionResponse = {
|
|
245
240
|
configOptions: this.#buildConfigOptions(record.session),
|
|
246
|
-
models: this.#buildModelState(record.session),
|
|
247
241
|
modes: this.#buildModeState(),
|
|
248
242
|
};
|
|
249
243
|
this.#scheduleBootstrapUpdates(record.session.sessionId);
|
|
@@ -256,14 +250,13 @@ export class AcpAgent implements Agent {
|
|
|
256
250
|
const response: ForkSessionResponse = {
|
|
257
251
|
sessionId: record.session.sessionId,
|
|
258
252
|
configOptions: this.#buildConfigOptions(record.session),
|
|
259
|
-
models: this.#buildModelState(record.session),
|
|
260
253
|
modes: this.#buildModeState(),
|
|
261
254
|
};
|
|
262
255
|
this.#scheduleBootstrapUpdates(record.session.sessionId);
|
|
263
256
|
return response;
|
|
264
257
|
}
|
|
265
258
|
|
|
266
|
-
async
|
|
259
|
+
async closeSession(params: CloseSessionRequest): Promise<CloseSessionResponse> {
|
|
267
260
|
const record = this.#sessions.get(params.sessionId);
|
|
268
261
|
if (!record) {
|
|
269
262
|
return {};
|
|
@@ -317,19 +310,6 @@ export class AcpAgent implements Agent {
|
|
|
317
310
|
return { configOptions };
|
|
318
311
|
}
|
|
319
312
|
|
|
320
|
-
async unstable_setSessionModel(params: SetSessionModelRequest): Promise<SetSessionModelResponse> {
|
|
321
|
-
const record = this.#getSessionRecord(params.sessionId);
|
|
322
|
-
await this.#setModelById(record.session, params.modelId);
|
|
323
|
-
await this.#connection.sessionUpdate({
|
|
324
|
-
sessionId: record.session.sessionId,
|
|
325
|
-
update: {
|
|
326
|
-
sessionUpdate: "config_option_update",
|
|
327
|
-
configOptions: this.#buildConfigOptions(record.session),
|
|
328
|
-
},
|
|
329
|
-
});
|
|
330
|
-
return {};
|
|
331
|
-
}
|
|
332
|
-
|
|
333
313
|
async prompt(params: PromptRequest): Promise<PromptResponse> {
|
|
334
314
|
const record = this.#getSessionRecord(params.sessionId);
|
|
335
315
|
if (record.promptTurn && !record.promptTurn.settled) {
|
|
@@ -339,7 +319,7 @@ export class AcpAgent implements Agent {
|
|
|
339
319
|
const converted = this.#convertPromptBlocks(params.prompt);
|
|
340
320
|
const pendingPrompt = Promise.withResolvers<PromptResponse>();
|
|
341
321
|
record.promptTurn = {
|
|
342
|
-
userMessageId:
|
|
322
|
+
userMessageId: crypto.randomUUID(),
|
|
343
323
|
cancelRequested: false,
|
|
344
324
|
settled: false,
|
|
345
325
|
usageBaseline: this.#cloneUsageStatistics(record.session.sessionManager.getUsageStatistics()),
|
|
@@ -371,7 +351,6 @@ export class AcpAgent implements Agent {
|
|
|
371
351
|
this.#finishPrompt(record, {
|
|
372
352
|
stopReason: "cancelled",
|
|
373
353
|
usage: this.#buildTurnUsage(promptTurn.usageBaseline, record.session.sessionManager.getUsageStatistics()),
|
|
374
|
-
userMessageId: promptTurn.userMessageId,
|
|
375
354
|
});
|
|
376
355
|
} catch (error: unknown) {
|
|
377
356
|
this.#finishPrompt(record, undefined, error);
|
|
@@ -563,7 +542,6 @@ export class AcpAgent implements Agent {
|
|
|
563
542
|
this.#finishPrompt(record, {
|
|
564
543
|
stopReason: promptTurn.cancelRequested ? "cancelled" : "end_turn",
|
|
565
544
|
usage: this.#buildTurnUsage(promptTurn.usageBaseline, record.session.sessionManager.getUsageStatistics()),
|
|
566
|
-
userMessageId: promptTurn.userMessageId,
|
|
567
545
|
});
|
|
568
546
|
}
|
|
569
547
|
}
|
|
@@ -674,28 +652,6 @@ export class AcpAgent implements Agent {
|
|
|
674
652
|
return configOptions;
|
|
675
653
|
}
|
|
676
654
|
|
|
677
|
-
#buildModelState(session: AgentSession): SessionModelState | undefined {
|
|
678
|
-
const models = session.getAvailableModels();
|
|
679
|
-
if (models.length === 0) {
|
|
680
|
-
return undefined;
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
const availableModels = models.map(model => ({
|
|
684
|
-
modelId: this.#toModelId(model),
|
|
685
|
-
name: model.name,
|
|
686
|
-
description: `${model.provider}/${model.id}`,
|
|
687
|
-
}));
|
|
688
|
-
const currentModelId = session.model ? this.#toModelId(session.model) : availableModels[0]?.modelId;
|
|
689
|
-
if (!currentModelId) {
|
|
690
|
-
return undefined;
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
return {
|
|
694
|
-
availableModels,
|
|
695
|
-
currentModelId,
|
|
696
|
-
};
|
|
697
|
-
}
|
|
698
|
-
|
|
699
655
|
#buildThinkingOptions(session: AgentSession): Array<{ value: string; name: string; description?: string }> {
|
|
700
656
|
return [
|
|
701
657
|
{ value: THINKING_OFF, name: "Off" },
|
|
@@ -1264,6 +1220,15 @@ export class AcpAgent implements Agent {
|
|
|
1264
1220
|
headers: this.#toNameValueMap(server.headers),
|
|
1265
1221
|
};
|
|
1266
1222
|
}
|
|
1223
|
+
// ACP 1.x added an `acp` transport, whose descriptor carries a serverId
|
|
1224
|
+
// instead of a url/headers pair. We never opt into it -- `initialize`
|
|
1225
|
+
// advertises only `mcpCapabilities.http` and `.sse` -- so reject it
|
|
1226
|
+
// explicitly rather than letting it fall through to the sse branch.
|
|
1227
|
+
if (server.type === "acp") {
|
|
1228
|
+
throw new Error(
|
|
1229
|
+
`Unsupported MCP transport "acp" for server "${server.name}": xcsh advertises only http and sse`,
|
|
1230
|
+
);
|
|
1231
|
+
}
|
|
1267
1232
|
return {
|
|
1268
1233
|
type: "sse",
|
|
1269
1234
|
url: server.url,
|
|
@@ -1301,7 +1266,6 @@ export class AcpAgent implements Agent {
|
|
|
1301
1266
|
this.#finishPrompt(record, {
|
|
1302
1267
|
stopReason: "cancelled",
|
|
1303
1268
|
usage: this.#buildTurnUsage(promptTurn.usageBaseline, record.session.sessionManager.getUsageStatistics()),
|
|
1304
|
-
userMessageId: promptTurn.userMessageId,
|
|
1305
1269
|
});
|
|
1306
1270
|
}
|
|
1307
1271
|
|