@4pm/cli 1.5.40 → 1.7.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.
Files changed (2) hide show
  1. package/dist/index.js +120 -38
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -28635,6 +28635,37 @@ function isValidRoleSet(roles) {
28635
28635
  return true;
28636
28636
  }
28637
28637
 
28638
+ // vendor/@4pm/constants/worker-pool.ts
28639
+ var AiJob = {
28640
+ /** Spec field/section/subagent suggest (one-shot). */
28641
+ SPEC_SUGGEST: "spec-suggest",
28642
+ /** Spec review — advisory (one-shot). */
28643
+ SPEC_REVIEW: "spec-review",
28644
+ /** Spec compose + create verdict (one-shot). */
28645
+ SPEC_COMPOSE: "spec-compose",
28646
+ /** Generators: WBS/est/proposal/feasibility/report (one-shot). */
28647
+ DOC_GENERATE: "doc-generate",
28648
+ /** Console prompt — full agent. */
28649
+ CONSOLE_AGENT: "console-agent",
28650
+ /** Git merge / AI conflict resolve — full agent. */
28651
+ GIT_MERGE: "git-merge",
28652
+ /** Graph add-link/add-code/delete-unused advice (one-shot). */
28653
+ GRAPH_SUGGEST: "graph-suggest",
28654
+ /** Subagent/skill content generate (one-shot). */
28655
+ AGENTS_GENERATE: "agents-generate",
28656
+ /** Template "Analyze impact" report — read-only agent. */
28657
+ ANALYZE_IMPACT: "analyze-impact",
28658
+ /** Template "Update" → branch + PR — write-capable agent. */
28659
+ TEMPLATE_UPDATE: "template-update",
28660
+ /** Autonomous-mode self-spawn — full agent. */
28661
+ AUTONOMOUS: "autonomous",
28662
+ /** Memo → community post compose (vision) — full agent. */
28663
+ MEMO_COMPOSE: "memo-compose",
28664
+ /** Outbound input review — the reviewer cli (one-shot). */
28665
+ OUTBOUND_REVIEW: "outbound-review"
28666
+ };
28667
+ var AI_JOBS = Object.values(AiJob);
28668
+
28638
28669
  // vendor/@4pm/constants/worker-tools.ts
28639
28670
  var WORKER_TOOL_CATALOG = [
28640
28671
  { id: "git", label: "Git", category: "vcs", versionArg: "--version", installPackage: null, installable: false, updatable: false },
@@ -33828,7 +33859,15 @@ var dispatchCommandRequestSchema = external_exports.object({
33828
33859
  * "AI settings" modal (per-user localStorage), layered over the profile default by the cli.
33829
33860
  * Only meaningful with `ai:true`.
33830
33861
  */
33831
- aiConfig: aiRunConfigSchema.optional()
33862
+ aiConfig: aiRunConfigSchema.optional(),
33863
+ /**
33864
+ * AI job (ADR-0284): which of the 13 AI functions this dispatch is — the caller stamps it (the
33865
+ * end user never picks). With `pick:"idle"` it drives the job-aware routing over the project's
33866
+ * worker-routing table (pools before lone machines → job priority → member index, with
33867
+ * exhaustion failover). Absent ⇒ the flat idle pool (backward compatible). Ignored with an
33868
+ * explicit `machineLinkId`.
33869
+ */
33870
+ job: external_exports.enum(AI_JOBS).optional()
33832
33871
  }).superRefine((v, ctx) => {
33833
33872
  if (v.machineLinkId == null === (v.pick == null)) {
33834
33873
  ctx.addIssue({
@@ -37844,10 +37883,19 @@ async function listDir(path, root) {
37844
37883
 
37845
37884
  // src/core/fs-read.ts
37846
37885
  import { readFile as readFile3 } from "fs/promises";
37847
- import { resolve as resolve2 } from "path";
37886
+ import { resolve as resolve2, sep as sep3 } from "path";
37848
37887
  var MAX_BYTES = 256 * 1024;
37849
- async function readWorkerFile(path) {
37850
- const target = resolve2(path);
37888
+ function resolveInRoot(root, relPath) {
37889
+ if (!root) return null;
37890
+ const base = resolve2(root);
37891
+ const target = resolve2(base, relPath || ".");
37892
+ return target === base || target.startsWith(base + sep3) ? target : null;
37893
+ }
37894
+ async function readWorkerFile(root, path) {
37895
+ const target = resolveInRoot(root, path);
37896
+ if (!target) {
37897
+ return { path, content: "", truncated: false };
37898
+ }
37851
37899
  try {
37852
37900
  const buf = await readFile3(target);
37853
37901
  const truncated = buf.byteLength > MAX_BYTES;
@@ -37860,16 +37908,16 @@ async function readWorkerFile(path) {
37860
37908
 
37861
37909
  // src/core/fs-write.ts
37862
37910
  import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
37863
- import { dirname as dirname3, resolve as resolve3, sep as sep3 } from "path";
37911
+ import { dirname as dirname3, resolve as resolve3, sep as sep4 } from "path";
37864
37912
  var MAX_BYTES2 = 2 * 1024 * 1024;
37865
- function resolveInRoot(root, relPath) {
37913
+ function resolveInRoot2(root, relPath) {
37866
37914
  if (!root) return null;
37867
37915
  const base = resolve3(root);
37868
37916
  const target = resolve3(base, relPath || ".");
37869
- return target === base || target.startsWith(base + sep3) ? target : null;
37917
+ return target === base || target.startsWith(base + sep4) ? target : null;
37870
37918
  }
37871
37919
  async function writeWorkerFile(root, relPath, content) {
37872
- const target = resolveInRoot(root, relPath);
37920
+ const target = resolveInRoot2(root, relPath);
37873
37921
  if (!target) {
37874
37922
  return { ok: false, path: relPath, bytes: 0, error: "path escapes the project root" };
37875
37923
  }
@@ -37888,12 +37936,12 @@ async function writeWorkerFile(root, relPath, content) {
37888
37936
 
37889
37937
  // src/core/fs-mutate.ts
37890
37938
  import { mkdir as mkdir3, writeFile as writeFile3, rename, rm, stat } from "fs/promises";
37891
- import { basename as basename3, dirname as dirname4, resolve as resolve4, sep as sep4 } from "path";
37939
+ import { basename as basename3, dirname as dirname4, resolve as resolve4, sep as sep5 } from "path";
37892
37940
  var MAX_BYTES3 = 2 * 1024 * 1024;
37893
- function resolveInRoot2(root, rel) {
37941
+ function resolveInRoot3(root, rel) {
37894
37942
  const base = resolve4(root);
37895
37943
  const target = resolve4(base, rel || ".");
37896
- return target === base || target.startsWith(base + sep4) ? target : null;
37944
+ return target === base || target.startsWith(base + sep5) ? target : null;
37897
37945
  }
37898
37946
  async function isDir(p) {
37899
37947
  try {
@@ -37916,13 +37964,13 @@ async function mutateFs(root, req) {
37916
37964
  try {
37917
37965
  switch (req.op) {
37918
37966
  case "mkdir": {
37919
- const target = resolveInRoot2(root, req.path ?? "");
37967
+ const target = resolveInRoot3(root, req.path ?? "");
37920
37968
  if (!target || target === resolve4(root)) return escaped(req.path ?? "");
37921
37969
  await mkdir3(target, { recursive: true });
37922
37970
  return { ok: true, path: target };
37923
37971
  }
37924
37972
  case "create": {
37925
- const target = resolveInRoot2(root, req.path ?? "");
37973
+ const target = resolveInRoot3(root, req.path ?? "");
37926
37974
  if (!target || target === resolve4(root)) return escaped(req.path ?? "");
37927
37975
  if (await exists(target)) return { ok: false, path: target, error: "already exists" };
37928
37976
  const content = req.content ?? "";
@@ -37934,19 +37982,19 @@ async function mutateFs(root, req) {
37934
37982
  return { ok: true, path: target };
37935
37983
  }
37936
37984
  case "move": {
37937
- const from = resolveInRoot2(root, req.from ?? "");
37938
- let to = resolveInRoot2(root, req.to ?? "");
37985
+ const from = resolveInRoot3(root, req.from ?? "");
37986
+ let to = resolveInRoot3(root, req.to ?? "");
37939
37987
  if (!from || !to) return escaped(req.from ?? req.to ?? "");
37940
37988
  if (from === resolve4(root)) return { ok: false, path: from, error: "cannot move the project root" };
37941
37989
  if (await isDir(to)) to = resolve4(to, basename3(from));
37942
- if (to !== resolve4(root) && !to.startsWith(resolve4(root) + sep4)) return escaped(req.to ?? "");
37990
+ if (to !== resolve4(root) && !to.startsWith(resolve4(root) + sep5)) return escaped(req.to ?? "");
37943
37991
  if (await exists(to)) return { ok: false, path: to, error: "destination already exists" };
37944
37992
  await mkdir3(dirname4(to), { recursive: true });
37945
37993
  await rename(from, to);
37946
37994
  return { ok: true, path: to };
37947
37995
  }
37948
37996
  case "delete": {
37949
- const target = resolveInRoot2(root, req.path ?? "");
37997
+ const target = resolveInRoot3(root, req.path ?? "");
37950
37998
  if (!target) return escaped(req.path ?? "");
37951
37999
  if (target === resolve4(root)) return { ok: false, path: target, error: "cannot delete the project root" };
37952
38000
  await rm(target, { recursive: true, force: true });
@@ -37962,12 +38010,12 @@ async function mutateFs(root, req) {
37962
38010
 
37963
38011
  // src/core/fs-transfer.ts
37964
38012
  import { mkdir as mkdir4, readFile as readFile4, stat as stat2, writeFile as writeFile4 } from "fs/promises";
37965
- import { basename as basename4, dirname as dirname5, extname, resolve as resolve5, sep as sep5 } from "path";
37966
- function resolveInRoot3(root, relPath) {
38013
+ import { basename as basename4, dirname as dirname5, extname, resolve as resolve5, sep as sep6 } from "path";
38014
+ function resolveInRoot4(root, relPath) {
37967
38015
  if (!root) return null;
37968
38016
  const base = resolve5(root);
37969
38017
  const target = resolve5(base, relPath || ".");
37970
- return target === base || target.startsWith(base + sep5) ? target : null;
38018
+ return target === base || target.startsWith(base + sep6) ? target : null;
37971
38019
  }
37972
38020
  var MIME_BY_EXT = {
37973
38021
  ".png": "image/png",
@@ -37991,7 +38039,7 @@ function guessMime(path) {
37991
38039
  return MIME_BY_EXT[extname(path).toLowerCase()] ?? "application/octet-stream";
37992
38040
  }
37993
38041
  async function uploadBinaryFile(root, relPath, contentBase64) {
37994
- const target = resolveInRoot3(root, relPath);
38042
+ const target = resolveInRoot4(root, relPath);
37995
38043
  if (!target) {
37996
38044
  return { ok: false, path: relPath, bytes: 0, error: "path escapes the project root" };
37997
38045
  }
@@ -38013,7 +38061,7 @@ async function uploadBinaryFile(root, relPath, contentBase64) {
38013
38061
  }
38014
38062
  }
38015
38063
  async function downloadBinaryFile(root, relPath) {
38016
- const target = resolveInRoot3(root, relPath);
38064
+ const target = resolveInRoot4(root, relPath);
38017
38065
  if (!target) return { error: "path escapes the project root" };
38018
38066
  try {
38019
38067
  const info = await stat2(target);
@@ -38042,7 +38090,7 @@ function handleFsChannels(ctx, message, payload) {
38042
38090
  );
38043
38091
  return true;
38044
38092
  case WsChannels.FS_READ:
38045
- void readWorkerFile(payload.path).then(
38093
+ void readWorkerFile(ctx.physicRoot, payload.path).then(
38046
38094
  (reply) => ctx.send(WsChannels.FS_READ, reply, message.id)
38047
38095
  );
38048
38096
  return true;
@@ -38082,29 +38130,63 @@ function handleFsChannels(ctx, message, payload) {
38082
38130
  // src/core/git-diff.ts
38083
38131
  import { readFile as readFile5 } from "fs/promises";
38084
38132
  import { execFile as execFile4 } from "child_process";
38085
- import { basename as basename5, dirname as dirname6, resolve as resolve6 } from "path";
38133
+ import { basename as basename5, dirname as dirname6, resolve as resolve6, sep as sep7 } from "path";
38086
38134
  import { promisify as promisify4 } from "util";
38087
38135
  var run4 = promisify4(execFile4);
38088
- async function gitDiff(path) {
38089
- const target = resolve6(path);
38136
+ function resolveInRoot5(root, relPath) {
38137
+ if (!root) return null;
38138
+ const base = resolve6(root);
38139
+ const target = resolve6(base, relPath || ".");
38140
+ return target === base || target.startsWith(base + sep7) ? target : null;
38141
+ }
38142
+ function looksBinary(buf) {
38143
+ return buf.subarray(0, 8e3).includes(0);
38144
+ }
38145
+ function cappedBase64(buf) {
38146
+ if (!buf || buf.byteLength > FS_TRANSFER_MAX_BYTES) return "";
38147
+ return buf.toString("base64");
38148
+ }
38149
+ async function gitDiff(root, path) {
38150
+ const target = resolveInRoot5(root, path);
38151
+ if (!target) {
38152
+ return { path, oldContent: "", newContent: "", isBinary: false };
38153
+ }
38090
38154
  const dir2 = dirname6(target);
38091
38155
  const base = basename5(target);
38092
- let newContent = "";
38156
+ let newBuf = null;
38093
38157
  try {
38094
- newContent = await readFile5(target, "utf8");
38158
+ newBuf = await readFile5(target);
38095
38159
  } catch {
38096
38160
  }
38097
- let oldContent = "";
38161
+ let oldBuf = null;
38098
38162
  try {
38099
38163
  const { stdout } = await run4("git", ["show", `HEAD:./${base}`], {
38100
38164
  cwd: dir2,
38101
38165
  timeout: 15e3,
38102
- maxBuffer: 4 * 1024 * 1024
38166
+ maxBuffer: FS_TRANSFER_MAX_BYTES + 1024,
38167
+ encoding: "buffer"
38103
38168
  });
38104
- oldContent = stdout;
38169
+ oldBuf = stdout;
38105
38170
  } catch {
38106
38171
  }
38107
- return { path: target, oldContent, newContent };
38172
+ const sample = newBuf ?? oldBuf;
38173
+ if (sample && looksBinary(sample)) {
38174
+ return {
38175
+ path: target,
38176
+ oldContent: "",
38177
+ newContent: "",
38178
+ isBinary: true,
38179
+ contentType: guessMime(target),
38180
+ oldContentBase64: cappedBase64(oldBuf),
38181
+ newContentBase64: cappedBase64(newBuf)
38182
+ };
38183
+ }
38184
+ return {
38185
+ path: target,
38186
+ oldContent: oldBuf ? oldBuf.toString("utf8") : "",
38187
+ newContent: newBuf ? newBuf.toString("utf8") : "",
38188
+ isBinary: false
38189
+ };
38108
38190
  }
38109
38191
 
38110
38192
  // src/core/git-env.ts
@@ -38209,7 +38291,7 @@ async function manageSshKey(op, profileDir2) {
38209
38291
  // src/core/git-history.ts
38210
38292
  import { readdir as readdir3 } from "fs/promises";
38211
38293
  import { execFile as execFile7 } from "child_process";
38212
- import { resolve as resolve7, sep as sep6 } from "path";
38294
+ import { resolve as resolve7, sep as sep8 } from "path";
38213
38295
  import { promisify as promisify7 } from "util";
38214
38296
  var run7 = promisify7(execFile7);
38215
38297
  var FS = "";
@@ -38218,7 +38300,7 @@ function resolveRepo(root, repo) {
38218
38300
  if (!root) return null;
38219
38301
  const base = resolve7(root);
38220
38302
  const requested = resolve7(base, repo && repo.trim() ? repo : ".");
38221
- return requested === base || requested.startsWith(base + sep6) ? requested : base;
38303
+ return requested === base || requested.startsWith(base + sep8) ? requested : base;
38222
38304
  }
38223
38305
  async function git(cwd2, args) {
38224
38306
  try {
@@ -38240,7 +38322,7 @@ async function gitRepos(root) {
38240
38322
  const add = async (dir2) => {
38241
38323
  const inside = await git(dir2, ["rev-parse", "--show-toplevel"]);
38242
38324
  const top = inside.trim();
38243
- if (!top || found.has(top) || !(top === base || top.startsWith(base + sep6))) return;
38325
+ if (!top || found.has(top) || !(top === base || top.startsWith(base + sep8))) return;
38244
38326
  found.add(top);
38245
38327
  const subdir = top === base ? "" : top.slice(base.length + 1);
38246
38328
  const remote = (await git(top, ["remote", "get-url", "origin"])).trim();
@@ -38327,7 +38409,7 @@ async function gitCommitDiff(root, repo, hash, path) {
38327
38409
  function handleGitChannels(ctx, message, payload) {
38328
38410
  switch (message.channel) {
38329
38411
  case WsChannels.GIT_DIFF:
38330
- void gitDiff(payload.path).then(
38412
+ void gitDiff(ctx.physicRoot, payload.path).then(
38331
38413
  (reply) => ctx.send(WsChannels.GIT_DIFF, reply, message.id)
38332
38414
  );
38333
38415
  return true;
@@ -43291,7 +43373,7 @@ var src_default = Yoga;
43291
43373
 
43292
43374
  // ../../node_modules/.pnpm/ink@5.2.1_@types+react@18.3.31_react@18.3.1/node_modules/ink/build/reconciler.js
43293
43375
  var import_react_reconciler = __toESM(require_react_reconciler(), 1);
43294
- var import_constants10 = __toESM(require_constants2(), 1);
43376
+ var import_constants11 = __toESM(require_constants2(), 1);
43295
43377
  import process3 from "process";
43296
43378
 
43297
43379
  // ../../node_modules/.pnpm/ansi-regex@6.2.2/node_modules/ansi-regex/index.js
@@ -44533,7 +44615,7 @@ var reconciler_default = (0, import_react_reconciler.default)({
44533
44615
  scheduleTimeout: setTimeout,
44534
44616
  cancelTimeout: clearTimeout,
44535
44617
  noTimeout: -1,
44536
- getCurrentEventPriority: () => import_constants10.DefaultEventPriority,
44618
+ getCurrentEventPriority: () => import_constants11.DefaultEventPriority,
44537
44619
  beforeActiveInstanceBlur() {
44538
44620
  },
44539
44621
  afterActiveInstanceBlur() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4pm/cli",
3
- "version": "1.5.40",
3
+ "version": "1.7.0",
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",