@4pm/cli 1.4.3 → 1.4.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.
Files changed (2) hide show
  1. package/dist/index.js +42 -1
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -33663,6 +33663,10 @@ var WsChannels = {
33663
33663
  USAGE_REPORT: "usage.report",
33664
33664
  // cli → server: Claude subscription usage snapshot (5h / weekly — ADR-0072)
33665
33665
  MACHINE_USAGE: "machine.usage",
33666
+ // cli → server: last-run result of an external tool (claude/codex/gh/glab/git — ADR-0223).
33667
+ // One-way, sent immediately after each direct tool invocation (last-wins per tool); the
33668
+ // server merges it into `MachineLink.usageSnapshot.toolHealth` for the admin pool lists.
33669
+ TOOL_HEALTH: "tool.health",
33666
33670
  // cli → server: durable per-command history record on finish (ADR-0072)
33667
33671
  COMMAND_HISTORY: "command.history",
33668
33672
  // cli → server: periodic upload of the cli's own JSONL log file (ADR-0122) — stored
@@ -34011,6 +34015,24 @@ async function runCommand(dispatch, emit) {
34011
34015
  });
34012
34016
  }
34013
34017
 
34018
+ // src/core/tool-health.ts
34019
+ var sink = null;
34020
+ function setToolHealthSink(fn) {
34021
+ sink = fn;
34022
+ }
34023
+ function reportToolResult(tool, ok, message) {
34024
+ try {
34025
+ const reason = (message ?? "").trim().slice(0, 200);
34026
+ sink?.({
34027
+ tool,
34028
+ ok,
34029
+ message: ok ? null : reason || "failed",
34030
+ at: (/* @__PURE__ */ new Date()).toISOString()
34031
+ });
34032
+ } catch {
34033
+ }
34034
+ }
34035
+
34014
34036
  // src/core/ai-stream.ts
34015
34037
  function assistantText(ev) {
34016
34038
  const parts = ev.message?.content ?? [];
@@ -34117,8 +34139,11 @@ function runAttempt(commandId, cmd, args, cwd2, env3, onChunk) {
34117
34139
  async function runAiFailover(plan, commandId, cwd2, handlers) {
34118
34140
  const total = plan.attempts.length;
34119
34141
  let finalExit = -1;
34142
+ let lastCmd = plan.attempts[0]?.cmd ?? "claude";
34143
+ let lastCaptured = "";
34120
34144
  for (let i = 0; i < total; i++) {
34121
34145
  const attempt = plan.attempts[i];
34146
+ lastCmd = attempt.cmd;
34122
34147
  if (total > 1) handlers.onAttemptStart(attempt.label, i, total, attempt.cmd);
34123
34148
  const parser = createAiStreamParser(attempt.cmd);
34124
34149
  let captured = "";
@@ -34135,17 +34160,26 @@ async function runAiFailover(plan, commandId, cwd2, handlers) {
34135
34160
  captured += tail;
34136
34161
  handlers.onChunk(tail);
34137
34162
  }
34163
+ lastCaptured = captured;
34138
34164
  if (finalExit === 0) {
34139
34165
  const usage = parser.usage();
34140
34166
  if (usage.tokens === 0) usage.tokens = estimateTokens(captured);
34167
+ reportToolResult(attempt.cmd, true);
34141
34168
  return { exitCode: 0, workedDir: attempt.dir, workedKey: attempt.key, workedCmd: attempt.cmd, usage };
34142
34169
  }
34143
34170
  const reason = isAuthFailure(captured) ? "auth" : isSessionLimit(captured) ? "limit" : null;
34144
34171
  if (!reason || i === total - 1) break;
34145
34172
  handlers.onAttemptFail(attempt.label, reason);
34146
34173
  }
34174
+ reportToolResult(lastCmd, false, summarizeAiFailure(lastCaptured, finalExit));
34147
34175
  return { exitCode: finalExit, workedDir: null, workedKey: null, workedCmd: null, usage: { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 } };
34148
34176
  }
34177
+ function summarizeAiFailure(captured, exitCode) {
34178
+ if (isAuthFailure(captured)) return "Not logged in";
34179
+ if (isSessionLimit(captured)) return "Usage limit reached";
34180
+ const firstLine = captured.split("\n").map((l) => l.trim()).find((l) => l.length > 0);
34181
+ return firstLine || `exited ${exitCode}`;
34182
+ }
34149
34183
 
34150
34184
  // src/core/ai-profile-state.ts
34151
34185
  import { existsSync as existsSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
@@ -35068,7 +35102,10 @@ async function runKnowledgeCompose(req, ai, physicRoot) {
35068
35102
  for (let i = 0; i < attempts.length; i++) {
35069
35103
  try {
35070
35104
  const { code, out, err } = await runOnce(ai.cmd, attempts[i], prompt, physicRoot, ai.env);
35071
- if (code === 0 && out.trim()) return { bodyMarkdown: out.trim() };
35105
+ if (code === 0 && out.trim()) {
35106
+ reportToolResult(ai.cmd, true);
35107
+ return { bodyMarkdown: out.trim() };
35108
+ }
35072
35109
  const combined = err || out;
35073
35110
  lastReason = `exited ${code}: ${combined.slice(0, 500)}`;
35074
35111
  if (!isAuthFailure(combined) && !isSessionLimit(combined) || i === attempts.length - 1) break;
@@ -35077,6 +35114,7 @@ async function runKnowledgeCompose(req, ai, physicRoot) {
35077
35114
  break;
35078
35115
  }
35079
35116
  }
35117
+ reportToolResult(ai.cmd, false, lastReason);
35080
35118
  return { bodyMarkdown: "", error: lastReason };
35081
35119
  }
35082
35120
 
@@ -36961,6 +36999,7 @@ var WsClient = class {
36961
36999
  socket.on("close", (code) => {
36962
37000
  this.clearHeartbeat();
36963
37001
  this.sessionKey = null;
37002
+ setToolHealthSink(null);
36964
37003
  logger.debug("ws.close", { code });
36965
37004
  if (code === CLOSE_DRAINING) {
36966
37005
  this.bus.log("Server draining \u2014 will reconnect (backoff + jitter).");
@@ -37002,6 +37041,7 @@ var WsClient = class {
37002
37041
  this.startUsagePolling();
37003
37042
  this.startLogUpload();
37004
37043
  this.startKbRefresh();
37044
+ setToolHealthSink((report) => this.send(WsChannels.TOOL_HEALTH, report));
37005
37045
  this.connectWaiters.splice(0).forEach((fn) => fn());
37006
37046
  return;
37007
37047
  }
@@ -37174,6 +37214,7 @@ var WsClient = class {
37174
37214
  this.bus.push({ source: "server", kind: "aires", text: `${supportCmd} \u203A` });
37175
37215
  this.bus.push({ source: "server", kind: "out", text: reply.body });
37176
37216
  }
37217
+ reportToolResult(supportCmd, !reply.error, reply.error);
37177
37218
  this.send(WsChannels.SUPPORT_ANSWER, reply, message.id);
37178
37219
  }).finally(() => this.bus.endBusy("support-answer"));
37179
37220
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4pm/cli",
3
- "version": "1.4.3",
3
+ "version": "1.4.4",
4
4
  "private": false,
5
5
  "description": "4PM CLI — drives Claude/Codex CLIs on AI worker machines (Node + TypeScript)",
6
6
  "license": "LicenseRef-4PM-Source-Available",