@mtreeai/msapling-cli 2.3.6-beta.47 → 2.3.6-beta.48

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.
Files changed (2) hide show
  1. package/dist/index.js +102 -10
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1032,6 +1032,47 @@ var init_src = __esm({
1032
1032
  body: JSON.stringify({ query, max_results: maxResults })
1033
1033
  });
1034
1034
  }
1035
+ /**
1036
+ * CLI-LOOP-01: cached backend capability map (from GET /chat/health). `null`
1037
+ * until first probed. Cached for the client's lifetime — capabilities are a
1038
+ * deploy-time property of the backend, so a single probe per session suffices.
1039
+ */
1040
+ _capabilities = null;
1041
+ _capabilitiesProbe = null;
1042
+ /**
1043
+ * CLI-LOOP-01: fetch (and cache) the backend capability map from
1044
+ * GET /chat/health. Returns an empty object when the backend is older / the
1045
+ * probe fails, so callers degrade to legacy behavior rather than throwing.
1046
+ *
1047
+ * Concurrent callers share a single in-flight probe.
1048
+ */
1049
+ async getCapabilities(force = false) {
1050
+ if (this._capabilities && !force) return this._capabilities;
1051
+ if (this._capabilitiesProbe && !force) return this._capabilitiesProbe;
1052
+ this._capabilitiesProbe = (async () => {
1053
+ try {
1054
+ const data = await this.request("/api/chat/health");
1055
+ const caps = data && typeof data === "object" && data.capabilities && typeof data.capabilities === "object" ? data.capabilities : {};
1056
+ this._capabilities = caps;
1057
+ return caps;
1058
+ } catch {
1059
+ this._capabilities = {};
1060
+ return this._capabilities;
1061
+ } finally {
1062
+ this._capabilitiesProbe = null;
1063
+ }
1064
+ })();
1065
+ return this._capabilitiesProbe;
1066
+ }
1067
+ /**
1068
+ * CLI-LOOP-01: convenience — does the backend accept the additive structured
1069
+ * `tool_results` array (native parallel multi-tool-use)? When false the Agent
1070
+ * loop uses the legacy `[TOOL_RESULT]`-per-prompt path.
1071
+ */
1072
+ async supportsStructuredToolResults() {
1073
+ const caps = await this.getCapabilities();
1074
+ return caps.structured_tool_results === true;
1075
+ }
1035
1076
  async getHistory(chatId) {
1036
1077
  const data = await this.request(`/api/projects/chat/${chatId}/history`);
1037
1078
  return data.messages.map((m) => ({
@@ -7407,8 +7448,15 @@ var init_Agent = __esm({
7407
7448
  }
7408
7449
  const config = await this.getProjectConfig();
7409
7450
  const MAX_WORKER_TURN_DEPTH = 25;
7451
+ let structuredToolResults = false;
7452
+ try {
7453
+ structuredToolResults = await this.client.supportsStructuredToolResults();
7454
+ } catch {
7455
+ structuredToolResults = false;
7456
+ }
7410
7457
  const queue = [prompt4];
7411
7458
  let rounds = 0;
7459
+ let toolCallSeq = 0;
7412
7460
  let streamUsage = null;
7413
7461
  while (queue.length > 0 && rounds < MAX_WORKER_TURN_DEPTH) {
7414
7462
  if (this.contextBudget.needsCompaction() && queue.length > 0) {
@@ -7452,14 +7500,25 @@ var init_Agent = __esm({
7452
7500
  this.contextBudget.reset();
7453
7501
  if (compactionSummary) {
7454
7502
  const next = queue[0];
7455
- queue[0] = `[CONTEXT_SUMMARY]: ${compactionSummary}
7503
+ if (typeof next === "string") {
7504
+ queue[0] = `[CONTEXT_SUMMARY]: ${compactionSummary}
7456
7505
 
7457
7506
  ${next}`;
7507
+ } else {
7508
+ queue[0] = {
7509
+ ...next,
7510
+ prompt: `[CONTEXT_SUMMARY]: ${compactionSummary}
7511
+
7512
+ ${next.prompt}`
7513
+ };
7514
+ }
7458
7515
  }
7459
7516
  continue;
7460
7517
  }
7461
- const currentPrompt = queue.shift();
7518
+ const currentItem = queue.shift();
7462
7519
  rounds++;
7520
+ const currentPrompt = typeof currentItem === "string" ? currentItem : currentItem.prompt;
7521
+ const currentToolResults = typeof currentItem === "string" ? void 0 : currentItem.toolResults;
7463
7522
  const stream = this.chatWithFallback(
7464
7523
  {
7465
7524
  chat_id: chatId,
@@ -7468,11 +7527,16 @@ ${next}`;
7468
7527
  tools: this.executor.getToolSchemas(),
7469
7528
  project_root: this.projectRoot,
7470
7529
  mode: this.executor.getMode(),
7530
+ // CLI-LOOP-01: carry the concurrently-executed tool_results from the
7531
+ // previous turn as a structured batch. Only set in the capability-
7532
+ // enabled path; absent => backend sees the legacy single-prompt turn.
7533
+ ...currentToolResults && currentToolResults.length > 0 ? { tool_results: currentToolResults } : {},
7471
7534
  ...config.combined ? { project_context: config.combined } : {}
7472
7535
  },
7473
7536
  chatId,
7474
7537
  currentPrompt
7475
7538
  );
7539
+ const pendingToolCalls = [];
7476
7540
  for await (const chunk of stream) {
7477
7541
  if (chunk.content) {
7478
7542
  onContent(chunk.content);
@@ -7489,11 +7553,39 @@ ${next}`;
7489
7553
  });
7490
7554
  }
7491
7555
  if (chunk.tool_use) {
7492
- const result = await this.executor.execute(
7493
- chunk.tool_use.name,
7494
- chunk.tool_use.args,
7495
- this.projectRoot
7496
- );
7556
+ const id = chunk.tool_use.id ?? `call_${rounds}_${toolCallSeq++}`;
7557
+ pendingToolCalls.push({ id, name: chunk.tool_use.name, args: chunk.tool_use.args });
7558
+ }
7559
+ }
7560
+ if (pendingToolCalls.length === 0) {
7561
+ continue;
7562
+ }
7563
+ if (structuredToolResults) {
7564
+ const toolResults = await Promise.all(
7565
+ pendingToolCalls.map(async (call) => {
7566
+ try {
7567
+ const result = await this.executor.execute(call.name, call.args, this.projectRoot);
7568
+ const redactedContent = SafetyGuard.redact(result.content);
7569
+ return {
7570
+ tool_use_id: call.id,
7571
+ name: call.name,
7572
+ content: redactedContent,
7573
+ is_error: !!result.isError
7574
+ };
7575
+ } catch (e) {
7576
+ return {
7577
+ tool_use_id: call.id,
7578
+ name: call.name,
7579
+ content: SafetyGuard.redact(`Tool execution failed: ${e?.message ?? String(e)}`),
7580
+ is_error: true
7581
+ };
7582
+ }
7583
+ })
7584
+ );
7585
+ queue.push({ prompt: "", toolResults });
7586
+ } else {
7587
+ for (const call of pendingToolCalls) {
7588
+ const result = await this.executor.execute(call.name, call.args, this.projectRoot);
7497
7589
  const redactedContent = SafetyGuard.redact(result.content);
7498
7590
  const redactedResult = { ...result, content: redactedContent };
7499
7591
  queue.push(`[TOOL_RESULT]: ${JSON.stringify(redactedResult)}`);
@@ -12114,7 +12206,7 @@ var init_version = __esm({
12114
12206
  description: "Show version information for CLI and core packages",
12115
12207
  category: "debug",
12116
12208
  handler: async (_args, context) => {
12117
- const cliVersion = true ? "2.3.6-beta.47" : "(dev)";
12209
+ const cliVersion = true ? "2.3.6-beta.48" : "(dev)";
12118
12210
  const coreVersion = true ? "2.3.6-beta.43" : "(dev)";
12119
12211
  const runtime = process.version;
12120
12212
  context.addMessage("system", "MSapling Version Info");
@@ -12123,7 +12215,7 @@ var init_version = __esm({
12123
12215
  context.addMessage("system", row2("Core (@msapling/core)", coreVersion));
12124
12216
  context.addMessage("system", row2("Runtime (Node/Bun)", runtime));
12125
12217
  try {
12126
- const ts = "2026-06-20T08:11:43.445Z";
12218
+ const ts = "2026-06-20T08:17:10.331Z";
12127
12219
  if (ts && ts !== "__BUILD_TIMESTAMP__") {
12128
12220
  context.addMessage("system", row2("Build Timestamp", ts));
12129
12221
  }
@@ -17917,7 +18009,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
17917
18009
  var Header = () => /* @__PURE__ */ jsxs(Box, { borderStyle: "single", borderColor: "cyan", paddingX: 1, marginBottom: 1, children: [
17918
18010
  /* @__PURE__ */ jsxs(Text, { bold: true, color: "cyan", children: [
17919
18011
  "\u25CF MSapling CLI v",
17920
- "2.3.6-beta.47"
18012
+ "2.3.6-beta.48"
17921
18013
  ] }),
17922
18014
  /* @__PURE__ */ jsx(Box, { marginLeft: 2, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "Platinum Tier Architecture" }) })
17923
18015
  ] });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mtreeai/msapling-cli",
3
- "version": "2.3.6-beta.47",
3
+ "version": "2.3.6-beta.48",
4
4
  "description": "MSapling CLI — React/Ink terminal client for the MSapling backend (chat, projects, MDrive, agent tools). Proprietary; redistribution prohibited.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "MSapling Team",