@4pm/cli 1.5.22 → 1.5.26
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/dist/index.js +93 -31
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -33013,6 +33013,15 @@ var dispatchCommandRequestSchema = external_exports.object({
|
|
|
33013
33013
|
* agent run (the Console tab + Git merge, which legitimately use tools).
|
|
33014
33014
|
*/
|
|
33015
33015
|
aiOneShot: external_exports.boolean().optional(),
|
|
33016
|
+
/**
|
|
33017
|
+
* Read-only agent AI mode (ADR-0265): the prompt is a task that must **read + inspect the repo
|
|
33018
|
+
* but write nothing** (the project-template "Analyze impact"). Unlike `aiOneShot` it keeps the
|
|
33019
|
+
* read/search tools (`Read`/`Glob`/`Grep`/`Bash`) and runs multi-turn, but the cli blocks the
|
|
33020
|
+
* write/orchestration tools and runs claude under `--permission-mode plan` (codex `--sandbox
|
|
33021
|
+
* read-only`) so it can diff files without editing them. Only meaningful with `ai:true`;
|
|
33022
|
+
* **mutually exclusive with `aiOneShot`** (a run is one-shot | read-only | full-agent).
|
|
33023
|
+
*/
|
|
33024
|
+
aiReadOnly: external_exports.boolean().optional(),
|
|
33016
33025
|
/**
|
|
33017
33026
|
* Per-run AI execution overrides (ADR-0261): model/thinking/temperature chosen in the web
|
|
33018
33027
|
* "AI settings" modal (per-user localStorage), layered over the profile default by the cli.
|
|
@@ -33027,6 +33036,13 @@ var dispatchCommandRequestSchema = external_exports.object({
|
|
|
33027
33036
|
message: "Provide exactly one of machineLinkId or pick."
|
|
33028
33037
|
});
|
|
33029
33038
|
}
|
|
33039
|
+
if (v.aiOneShot && v.aiReadOnly) {
|
|
33040
|
+
ctx.addIssue({
|
|
33041
|
+
code: external_exports.ZodIssueCode.custom,
|
|
33042
|
+
path: ["aiReadOnly"],
|
|
33043
|
+
message: "aiOneShot and aiReadOnly are mutually exclusive."
|
|
33044
|
+
});
|
|
33045
|
+
}
|
|
33030
33046
|
if (!v.ai && v.command.length > COMMAND_MAX_LEN) {
|
|
33031
33047
|
ctx.addIssue({
|
|
33032
33048
|
code: external_exports.ZodIssueCode.too_big,
|
|
@@ -33318,16 +33334,6 @@ var MEMO_MAX_LENGTH = 65536;
|
|
|
33318
33334
|
var putMemoRequestSchema = external_exports.object({
|
|
33319
33335
|
content: external_exports.string().max(MEMO_MAX_LENGTH)
|
|
33320
33336
|
});
|
|
33321
|
-
var MEMO_ITEM_MAX_LENGTH = 2e3;
|
|
33322
|
-
var createMemoItemSchema = external_exports.object({
|
|
33323
|
-
content: external_exports.string().trim().min(1).max(MEMO_ITEM_MAX_LENGTH)
|
|
33324
|
-
});
|
|
33325
|
-
var updateMemoItemSchema = external_exports.object({
|
|
33326
|
-
content: external_exports.string().trim().min(1).max(MEMO_ITEM_MAX_LENGTH).optional(),
|
|
33327
|
-
done: external_exports.boolean().optional()
|
|
33328
|
-
}).refine((v) => v.content !== void 0 || v.done !== void 0, {
|
|
33329
|
-
message: "content or done required"
|
|
33330
|
-
});
|
|
33331
33337
|
var HTTP_JSON_BODY_LIMIT_BYTES = 2 * 1048576;
|
|
33332
33338
|
var putSpecDraftRequestSchema = external_exports.object({
|
|
33333
33339
|
spec: external_exports.record(external_exports.unknown()),
|
|
@@ -34839,9 +34845,57 @@ var ONE_SHOT_DISALLOWED_CLAUDE_TOOLS = [
|
|
|
34839
34845
|
"SendMessage",
|
|
34840
34846
|
"PushNotification"
|
|
34841
34847
|
].join(",");
|
|
34848
|
+
var ONE_SHOT_MAX_TURNS = 4;
|
|
34842
34849
|
function oneShotArgs(cmd) {
|
|
34843
34850
|
if (!cmd.includes("claude")) return [];
|
|
34844
|
-
return [
|
|
34851
|
+
return [
|
|
34852
|
+
`--max-turns=${ONE_SHOT_MAX_TURNS}`,
|
|
34853
|
+
"--disallowedTools",
|
|
34854
|
+
ONE_SHOT_DISALLOWED_CLAUDE_TOOLS,
|
|
34855
|
+
"--permission-mode",
|
|
34856
|
+
"default"
|
|
34857
|
+
];
|
|
34858
|
+
}
|
|
34859
|
+
var READ_ONLY_DISALLOWED_CLAUDE_TOOLS = [
|
|
34860
|
+
"Edit",
|
|
34861
|
+
"Write",
|
|
34862
|
+
"NotebookEdit",
|
|
34863
|
+
"Task",
|
|
34864
|
+
"SlashCommand",
|
|
34865
|
+
"Skill",
|
|
34866
|
+
"WebSearch",
|
|
34867
|
+
"WebFetch",
|
|
34868
|
+
"TaskCreate",
|
|
34869
|
+
"TaskGet",
|
|
34870
|
+
"TaskList",
|
|
34871
|
+
"TaskOutput",
|
|
34872
|
+
"TaskStop",
|
|
34873
|
+
"TaskUpdate",
|
|
34874
|
+
"Monitor",
|
|
34875
|
+
"DesignSync",
|
|
34876
|
+
"CronCreate",
|
|
34877
|
+
"CronDelete",
|
|
34878
|
+
"CronList",
|
|
34879
|
+
"EnterWorktree",
|
|
34880
|
+
"ExitWorktree",
|
|
34881
|
+
"RemoteTrigger",
|
|
34882
|
+
"ScheduleWakeup",
|
|
34883
|
+
"SendMessage",
|
|
34884
|
+
"PushNotification"
|
|
34885
|
+
].join(",");
|
|
34886
|
+
var READ_ONLY_MAX_TURNS = 40;
|
|
34887
|
+
function readOnlyArgs(cmd) {
|
|
34888
|
+
if (cmd.includes("claude")) {
|
|
34889
|
+
return [
|
|
34890
|
+
`--max-turns=${READ_ONLY_MAX_TURNS}`,
|
|
34891
|
+
"--disallowedTools",
|
|
34892
|
+
READ_ONLY_DISALLOWED_CLAUDE_TOOLS,
|
|
34893
|
+
"--permission-mode",
|
|
34894
|
+
"plan"
|
|
34895
|
+
];
|
|
34896
|
+
}
|
|
34897
|
+
if (cmd.includes("codex")) return ["--sandbox", "read-only"];
|
|
34898
|
+
return [];
|
|
34845
34899
|
}
|
|
34846
34900
|
function requiredArgs(cmd) {
|
|
34847
34901
|
if (cmd.includes("claude")) return REQUIRED_AI_ARGS.claude;
|
|
@@ -34916,21 +34970,19 @@ function overrideEnv(cmd, o) {
|
|
|
34916
34970
|
const budget = THINKING_TOKENS[o.thinking];
|
|
34917
34971
|
return budget ? { MAX_THINKING_TOKENS: String(budget) } : {};
|
|
34918
34972
|
}
|
|
34919
|
-
function buildRunArgs(profile, cmd, resumeId, oneShot = false, override) {
|
|
34973
|
+
function buildRunArgs(profile, cmd, resumeId, oneShot = false, override, readOnly = false) {
|
|
34920
34974
|
const extras = profile.args ?? [];
|
|
34921
34975
|
const model = override?.model?.trim() || profile.model?.trim();
|
|
34922
34976
|
const modelArgs = model ? ["--model", model] : [];
|
|
34923
34977
|
const resumeArgs = resumeId && cmd.includes("claude") ? ["--resume", resumeId] : [];
|
|
34924
|
-
const
|
|
34925
|
-
const perProfileTurns = !oneShot && cmd.includes("claude") && typeof turns === "number" && turns > 0 ? [`--max-turns=${Math.floor(turns)}`] : [];
|
|
34926
|
-
const oneShotExtra = oneShot ? oneShotArgs(cmd) : perProfileTurns;
|
|
34978
|
+
const modeExtra = oneShot ? oneShotArgs(cmd) : readOnly ? readOnlyArgs(cmd) : [];
|
|
34927
34979
|
return [
|
|
34928
34980
|
...requiredArgs(cmd),
|
|
34929
34981
|
...resumeArgs,
|
|
34930
34982
|
...extras,
|
|
34931
34983
|
...modelArgs,
|
|
34932
34984
|
...overrideArgs(cmd, override),
|
|
34933
|
-
...
|
|
34985
|
+
...modeExtra
|
|
34934
34986
|
];
|
|
34935
34987
|
}
|
|
34936
34988
|
function claudeHomeDirs(config) {
|
|
@@ -34970,10 +35022,10 @@ function resolveClaudeProfiles(config, workingDir) {
|
|
|
34970
35022
|
if (idx > 0) profiles.unshift(profiles.splice(idx, 1)[0]);
|
|
34971
35023
|
return profiles;
|
|
34972
35024
|
}
|
|
34973
|
-
function planAiRun(prompt, config, hint = {}, resume = /* @__PURE__ */ new Map(), oneShot = false, override) {
|
|
34974
|
-
return isUnifiedConfig(config) ? planUnifiedRun(prompt, config, hint.credential ?? null, resume, oneShot, override) : planLegacyRun(prompt, config, hint.dir ?? null, resume, oneShot, override);
|
|
35025
|
+
function planAiRun(prompt, config, hint = {}, resume = /* @__PURE__ */ new Map(), oneShot = false, override, readOnly = false) {
|
|
35026
|
+
return isUnifiedConfig(config) ? planUnifiedRun(prompt, config, hint.credential ?? null, resume, oneShot, override, readOnly) : planLegacyRun(prompt, config, hint.dir ?? null, resume, oneShot, override, readOnly);
|
|
34975
35027
|
}
|
|
34976
|
-
function planUnifiedRun(prompt, config, workingCredential, resume, oneShot, override) {
|
|
35028
|
+
function planUnifiedRun(prompt, config, workingCredential, resume, oneShot, override, readOnly = false) {
|
|
34977
35029
|
const baseEnv = config.aiEnv ?? {};
|
|
34978
35030
|
const active = activeProvider(config);
|
|
34979
35031
|
const usable = config.aiProfiles.filter(isUsableCredential);
|
|
@@ -34982,7 +35034,7 @@ function planUnifiedRun(prompt, config, workingCredential, resume, oneShot, over
|
|
|
34982
35034
|
return {
|
|
34983
35035
|
cmd: active,
|
|
34984
35036
|
attempts: [
|
|
34985
|
-
{ cmd: active, label: "default", dir: null, key: null, args: buildRunArgs({ profile: "" }, active, void 0, oneShot, override), stdin: prompt, env: { ...baseEnv, ...overrideEnv(active, override) } }
|
|
35037
|
+
{ cmd: active, label: "default", dir: null, key: null, args: buildRunArgs({ profile: "" }, active, void 0, oneShot, override, readOnly), stdin: prompt, env: { ...baseEnv, ...overrideEnv(active, override) } }
|
|
34986
35038
|
]
|
|
34987
35039
|
};
|
|
34988
35040
|
}
|
|
@@ -35000,20 +35052,20 @@ function planUnifiedRun(prompt, config, workingCredential, resume, oneShot, over
|
|
|
35000
35052
|
label: cred.label?.trim() || profileDisplayLabel(dir2),
|
|
35001
35053
|
dir: dir2,
|
|
35002
35054
|
key,
|
|
35003
|
-
args: buildRunArgs(cred, cmd, resume.get(key), oneShot, override),
|
|
35055
|
+
args: buildRunArgs(cred, cmd, resume.get(key), oneShot, override, readOnly),
|
|
35004
35056
|
stdin: prompt,
|
|
35005
35057
|
env: envVar ? { ...baseEnv, [envVar]: dir2, ...overrideEnv(cmd, override) } : { ...baseEnv, ...overrideEnv(cmd, override) }
|
|
35006
35058
|
}));
|
|
35007
35059
|
return { cmd: attempts[0]?.cmd ?? DEFAULT_AI_CLI, attempts };
|
|
35008
35060
|
}
|
|
35009
|
-
function planLegacyRun(prompt, config, workingDir, resume, oneShot, override) {
|
|
35061
|
+
function planLegacyRun(prompt, config, workingDir, resume, oneShot, override, readOnly = false) {
|
|
35010
35062
|
const cmd = config.aiCli || DEFAULT_AI_CLI;
|
|
35011
35063
|
const baseEnv = config.aiEnv ?? {};
|
|
35012
35064
|
const envVar = profileEnvVar(cmd);
|
|
35013
35065
|
const defaultPlan = {
|
|
35014
35066
|
cmd,
|
|
35015
35067
|
attempts: [
|
|
35016
|
-
{ cmd, label: "default", dir: null, key: null, args: buildRunArgs({ profile: "" }, cmd, void 0, oneShot, override), stdin: prompt, env: { ...baseEnv, ...overrideEnv(cmd, override) } }
|
|
35068
|
+
{ cmd, label: "default", dir: null, key: null, args: buildRunArgs({ profile: "" }, cmd, void 0, oneShot, override, readOnly), stdin: prompt, env: { ...baseEnv, ...overrideEnv(cmd, override) } }
|
|
35017
35069
|
]
|
|
35018
35070
|
};
|
|
35019
35071
|
if (!envVar) return defaultPlan;
|
|
@@ -35030,7 +35082,7 @@ function planLegacyRun(prompt, config, workingDir, resume, oneShot, override) {
|
|
|
35030
35082
|
label: profileDisplayLabel(dir2),
|
|
35031
35083
|
dir: dir2,
|
|
35032
35084
|
key,
|
|
35033
|
-
args: buildRunArgs(profile, cmd, resume.get(key), oneShot, override),
|
|
35085
|
+
args: buildRunArgs(profile, cmd, resume.get(key), oneShot, override, readOnly),
|
|
35034
35086
|
stdin: prompt,
|
|
35035
35087
|
env: { ...baseEnv, [envVar]: dir2, ...overrideEnv(cmd, override) }
|
|
35036
35088
|
};
|
|
@@ -37922,7 +37974,9 @@ function toDtoEntry(e) {
|
|
|
37922
37974
|
level: e.level,
|
|
37923
37975
|
resultKind: e.resultKind,
|
|
37924
37976
|
// Processing time on a terminal `exit` entry (ADR-0249) — the web renders it inline.
|
|
37925
|
-
...e.durationMs != null ? { durationMs: e.durationMs } : {}
|
|
37977
|
+
...e.durationMs != null ? { durationMs: e.durationMs } : {},
|
|
37978
|
+
// AI-run metadata on an `aireq` marker — powers the web's clickable CLI name → details modal.
|
|
37979
|
+
...e.aiMeta ? { aiMeta: e.aiMeta } : {}
|
|
37926
37980
|
};
|
|
37927
37981
|
}
|
|
37928
37982
|
var WsClient = class {
|
|
@@ -38443,7 +38497,8 @@ var WsClient = class {
|
|
|
38443
38497
|
"server",
|
|
38444
38498
|
dispatch.aiOneShot ?? false,
|
|
38445
38499
|
dispatch.images,
|
|
38446
|
-
dispatch.aiConfig
|
|
38500
|
+
dispatch.aiConfig,
|
|
38501
|
+
dispatch.aiReadOnly ?? false
|
|
38447
38502
|
);
|
|
38448
38503
|
}
|
|
38449
38504
|
break;
|
|
@@ -38940,7 +38995,7 @@ var WsClient = class {
|
|
|
38940
38995
|
* exactly like typing the prompt in the cli (tries profiles, meters real tokens) instead
|
|
38941
38996
|
* of spawning the text as a raw command. Streams output to the transcript AND the server.
|
|
38942
38997
|
*/
|
|
38943
|
-
async runAiPrompt(prompt, commandId, origin, oneShot = false, images, aiConfig) {
|
|
38998
|
+
async runAiPrompt(prompt, commandId, origin, oneShot = false, images, aiConfig, readOnly = false) {
|
|
38944
38999
|
const config = readProfileConfig(this.context.profileDir);
|
|
38945
39000
|
const projectTimeoutSec = config.projectAiRunTimeoutSec ?? 0;
|
|
38946
39001
|
const machineTimeoutSec = config.aiRunTimeoutSec ?? 0;
|
|
@@ -39012,7 +39067,7 @@ var WsClient = class {
|
|
|
39012
39067
|
const guardedPrompt = this.restrictToFolder && this.physicRoot ? folderScopeGuard(this.physicRoot, aiBodyPrompt) : aiBodyPrompt;
|
|
39013
39068
|
const hint = unified ? { credential: startCred ?? workingCred } : { dir: startDir ?? workingDir };
|
|
39014
39069
|
const memCfg = resolveMemoryConfig(config);
|
|
39015
|
-
const firstAttempt = planAiRun(guardedPrompt, config, hint, /* @__PURE__ */ new Map(), oneShot, aiConfig).attempts[0];
|
|
39070
|
+
const firstAttempt = planAiRun(guardedPrompt, config, hint, /* @__PURE__ */ new Map(), oneShot, aiConfig, readOnly).attempts[0];
|
|
39016
39071
|
const resumeId = memCfg.enabled && firstAttempt?.key && firstAttempt.cmd === "claude" ? this.sessionIdByKey.get(firstAttempt.key) : void 0;
|
|
39017
39072
|
let effectivePrompt = guardedPrompt;
|
|
39018
39073
|
let resume = /* @__PURE__ */ new Map();
|
|
@@ -39024,10 +39079,17 @@ ${this.aiMemory}
|
|
|
39024
39079
|
|
|
39025
39080
|
${guardedPrompt}`;
|
|
39026
39081
|
}
|
|
39027
|
-
const plan = planAiRun(effectivePrompt, config, hint, resume, oneShot, aiConfig);
|
|
39082
|
+
const plan = planAiRun(effectivePrompt, config, hint, resume, oneShot, aiConfig, readOnly);
|
|
39028
39083
|
const markerArgs = plan.attempts[0]?.args ?? [];
|
|
39029
39084
|
const startStamp = formatTimestampInZone(/* @__PURE__ */ new Date(), this.orgTimezone);
|
|
39030
|
-
this.bus.push({
|
|
39085
|
+
this.bus.push({
|
|
39086
|
+
source: origin,
|
|
39087
|
+
kind: "aireq",
|
|
39088
|
+
text: `${startStamp} ${plan.cmd} \u2039 ${prompt}`,
|
|
39089
|
+
// Carry the run's prompt + resolved flags so the web Console's clickable CLI name can open a
|
|
39090
|
+
// details modal (the representative first-attempt argv; failover may run a different profile's).
|
|
39091
|
+
aiMeta: { cmd: plan.cmd, args: markerArgs, prompt }
|
|
39092
|
+
});
|
|
39031
39093
|
logger.info("command.ai", { commandId, origin, cmd: plan.cmd, profiles: plan.attempts.length });
|
|
39032
39094
|
if (origin === "local") {
|
|
39033
39095
|
this.send(WsChannels.COMMAND_ANNOUNCE, {
|
|
@@ -39085,7 +39147,7 @@ ${guardedPrompt}`;
|
|
|
39085
39147
|
`, log: true });
|
|
39086
39148
|
},
|
|
39087
39149
|
onAttemptFail: (label, reason) => {
|
|
39088
|
-
const text = reason === "limit" ? `profile "${label}" hit its session limit \u2014 trying next` : reason === "credits" ? `profile "${label}" is out of usage credits \u2014 trying next` : `profile "${label}" failed to authenticate \u2014 trying next`;
|
|
39150
|
+
const text = reason === "limit" ? `profile "${label}" hit its session limit \u2014 trying next` : reason === "credits" ? `profile "${label}" is out of usage credits \u2014 trying next` : reason === "auth" ? `profile "${label}" failed to authenticate \u2014 trying next` : `profile "${label}" run failed \u2014 trying next`;
|
|
39089
39151
|
this.bus.push({ source: origin, kind: "log", text, level: "warn" });
|
|
39090
39152
|
this.send(WsChannels.COMMAND_OUTPUT, { commandId, seq: seq++, chunk: `${text}
|
|
39091
39153
|
`, log: true });
|