@phnx-labs/agents-cli 1.22.22 → 1.22.23
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/CHANGELOG.md +209 -0
- package/README.md +8 -2
- package/dist/commands/doctor.js +15 -7
- package/dist/commands/exec.js +20 -6
- package/dist/commands/focus.d.ts +76 -4
- package/dist/commands/focus.js +219 -40
- package/dist/commands/fork.d.ts +20 -2
- package/dist/commands/fork.js +91 -64
- package/dist/commands/go.d.ts +25 -0
- package/dist/commands/go.js +63 -2
- package/dist/commands/harness-wizard.d.ts +206 -0
- package/dist/commands/harness-wizard.js +403 -0
- package/dist/commands/harness.d.ts +12 -0
- package/dist/commands/harness.js +97 -101
- package/dist/commands/resume.js +10 -3
- package/dist/commands/secrets.js +25 -30
- package/dist/commands/sessions-resume.d.ts +21 -3
- package/dist/commands/sessions-resume.js +50 -11
- package/dist/commands/sessions.d.ts +81 -5
- package/dist/commands/sessions.js +325 -66
- package/dist/commands/watchdog.js +13 -2
- package/dist/lib/agents.d.ts +1 -1
- package/dist/lib/agents.js +132 -0
- package/dist/lib/codex-policy.d.ts +17 -0
- package/dist/lib/codex-policy.js +48 -0
- package/dist/lib/crabbox/lease.d.ts +25 -0
- package/dist/lib/crabbox/lease.js +62 -0
- package/dist/lib/daemon.js +70 -0
- package/dist/lib/exec.d.ts +4 -0
- package/dist/lib/exec.js +88 -54
- package/dist/lib/feed-broadcast.d.ts +1 -20
- package/dist/lib/feed-broadcast.js +31 -1
- package/dist/lib/hooks.js +12 -2
- package/dist/lib/mcp.js +44 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +1 -5
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +2 -15
- package/dist/lib/models.d.ts +0 -5
- package/dist/lib/models.js +48 -0
- package/dist/lib/plugin-marketplace.js +9 -0
- package/dist/lib/pricing/prices.json +119 -92
- package/dist/lib/pricing/table.js +13 -0
- package/dist/lib/remote-agents-json.d.ts +29 -1
- package/dist/lib/remote-agents-json.js +47 -10
- package/dist/lib/resources/mcp.js +2 -0
- package/dist/lib/resources/permissions.js +3 -0
- package/dist/lib/resources/types.d.ts +2 -1
- package/dist/lib/runner.js +28 -19
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -2
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -13
- package/dist/lib/secrets/index.d.ts +15 -1
- package/dist/lib/secrets/index.js +118 -49
- package/dist/lib/secrets/reaper.d.ts +87 -0
- package/dist/lib/secrets/reaper.js +184 -0
- package/dist/lib/secrets/remote.d.ts +29 -0
- package/dist/lib/secrets/remote.js +37 -1
- package/dist/lib/session/active.d.ts +36 -1
- package/dist/lib/session/active.js +60 -19
- package/dist/lib/session/actor-sidecar.d.ts +14 -0
- package/dist/lib/session/actor-sidecar.js +67 -4
- package/dist/lib/session/db.d.ts +1 -1
- package/dist/lib/session/db.js +32 -1
- package/dist/lib/session/discover.js +168 -0
- package/dist/lib/session/parse.d.ts +10 -0
- package/dist/lib/session/parse.js +98 -0
- package/dist/lib/session/remote-list.d.ts +10 -1
- package/dist/lib/session/remote-list.js +2 -8
- package/dist/lib/session/remote.d.ts +57 -3
- package/dist/lib/session/remote.js +90 -26
- package/dist/lib/session/resume-command.d.ts +6 -0
- package/dist/lib/session/resume-command.js +8 -0
- package/dist/lib/session/session-cache.d.ts +173 -0
- package/dist/lib/session/session-cache.js +399 -0
- package/dist/lib/session/types.d.ts +1 -1
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/session/width.d.ts +1 -1
- package/dist/lib/session/width.js +12 -2
- package/dist/lib/shims.d.ts +2 -2
- package/dist/lib/shims.js +40 -5
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/types.d.ts +1 -1
- package/dist/lib/usage.d.ts +13 -0
- package/dist/lib/usage.js +215 -0
- package/dist/lib/versions.js +13 -2
- package/package.json +1 -1
- package/dist/bin/agents +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
package/dist/lib/exec.js
CHANGED
|
@@ -17,11 +17,11 @@ import { isTierToken, resolveTier } from './model-tiers.js';
|
|
|
17
17
|
import { createTimer, redactPrompt, redactArgs } from './events.js';
|
|
18
18
|
import { sanitizeProcessEnv } from './secrets/bundles.js';
|
|
19
19
|
import { resolveActor, actorEnv } from './actor.js';
|
|
20
|
-
import { getShimsDir, getHistoryDir
|
|
20
|
+
import { getShimsDir, getHistoryDir } from './state.js';
|
|
21
21
|
import { resolveCodexHome } from './codex-home.js';
|
|
22
22
|
import { readCodexConfiguredModel } from './shims.js';
|
|
23
23
|
import { writePidSessionEntry, extractSessionIdArg } from './session/pid-registry.js';
|
|
24
|
-
import { writeSessionActorRecord } from './session/actor-sidecar.js';
|
|
24
|
+
import { writeSessionActorRecord, writeSessionAliasRecord } from './session/actor-sidecar.js';
|
|
25
25
|
import { loadHookSessionIndex, resolveHookSessionId } from './session/hook-sessions.js';
|
|
26
26
|
import { sessionIdMarkerLine } from './hosts/session-marker.js';
|
|
27
27
|
import { recordRunName } from './session/run-names.js';
|
|
@@ -30,6 +30,7 @@ import { composeWin32CommandLine } from './platform/index.js';
|
|
|
30
30
|
import { isTmuxInstalled } from './tmux/binary.js';
|
|
31
31
|
import { shellQuote } from './ssh-exec.js';
|
|
32
32
|
import { resolveClaudeSetupToken } from './claude-account-token.js';
|
|
33
|
+
import { codexEditWritableRoots, codexPolicyArgs } from './codex-policy.js';
|
|
33
34
|
/**
|
|
34
35
|
* Map a raw mode string (CLI flag, YAML field, env var) to the canonical Mode.
|
|
35
36
|
*
|
|
@@ -172,6 +173,10 @@ export function resolveHeadlessMode(agent, requested, interactive, warningContex
|
|
|
172
173
|
export function defaultModeFor(agent) {
|
|
173
174
|
return AGENTS[agent].capabilities.modes[0];
|
|
174
175
|
}
|
|
176
|
+
/** Safe mode used when the user did not provide --mode or a configured default. */
|
|
177
|
+
export function implicitModeFor(agent) {
|
|
178
|
+
return agent === 'codex' ? 'edit' : 'plan';
|
|
179
|
+
}
|
|
175
180
|
/**
|
|
176
181
|
* Resolve interactive vs headless. Explicit flags are definitive and win over
|
|
177
182
|
* inference: `--interactive` forces interactive, `--headless` forces headless.
|
|
@@ -441,11 +446,11 @@ export const AGENT_COMMANDS = {
|
|
|
441
446
|
promptFlag: 'positional',
|
|
442
447
|
resume: { subcommand: 'resume' },
|
|
443
448
|
modeFlags: {
|
|
444
|
-
|
|
445
|
-
//
|
|
446
|
-
//
|
|
447
|
-
|
|
448
|
-
edit: [
|
|
449
|
+
// Native Codex modes are assembled by codexPolicyArgs below. Named
|
|
450
|
+
// permission profiles keep filesystem access and network access
|
|
451
|
+
// independent; legacy --sandbox flags cannot express that combination.
|
|
452
|
+
plan: [],
|
|
453
|
+
edit: [],
|
|
449
454
|
// skip = codex --yolo: drops the sandbox entirely and approves anything.
|
|
450
455
|
skip: ['--dangerously-bypass-approvals-and-sandbox'],
|
|
451
456
|
},
|
|
@@ -637,6 +642,29 @@ export const AGENT_COMMANDS = {
|
|
|
637
642
|
},
|
|
638
643
|
modelFlag: '--model',
|
|
639
644
|
},
|
|
645
|
+
// Meta Muse Code (`muse exec` headless, `muse` TUI). Flags from
|
|
646
|
+
// `muse --help` / `muse exec --help` (v0.1.0): prompt is positional;
|
|
647
|
+
// --model selects muse-spark-*; --reasoning-effort is the effort dial;
|
|
648
|
+
// --disable-write approximates plan (no non-shell writes); --disable-approval
|
|
649
|
+
// keeps the sandbox (auto); --yolo drops approval+sandbox and trusts the
|
|
650
|
+
// workspace (skip). --json emits JSONL on stdout. Interactive resume is
|
|
651
|
+
// `muse resume <id>`; headless resume is `muse exec --session-id <id>` —
|
|
652
|
+
// see the muse resume branch in buildExecCommand.
|
|
653
|
+
muse: {
|
|
654
|
+
base: ['muse', 'exec'],
|
|
655
|
+
promptFlag: 'positional',
|
|
656
|
+
modeFlags: {
|
|
657
|
+
plan: ['--disable-write'],
|
|
658
|
+
edit: [],
|
|
659
|
+
auto: ['--disable-approval'],
|
|
660
|
+
skip: ['--yolo'],
|
|
661
|
+
},
|
|
662
|
+
jsonFlags: ['--json'],
|
|
663
|
+
modelFlag: '--model',
|
|
664
|
+
// Flag form covers headless (`--session-id`). Interactive uses the
|
|
665
|
+
// `resume` subcommand — special-cased in buildExecCommand.
|
|
666
|
+
resume: { flag: '--session-id' },
|
|
667
|
+
},
|
|
640
668
|
};
|
|
641
669
|
/**
|
|
642
670
|
* Whether `agent` has a native resume form (Tier 1). Derived solely from the
|
|
@@ -665,12 +693,13 @@ export function buildExecCommand(options) {
|
|
|
665
693
|
const template = AGENT_COMMANDS[options.agent];
|
|
666
694
|
const cmd = [...template.base];
|
|
667
695
|
const interactive = resolveInteractive(options);
|
|
668
|
-
// For Codex and
|
|
669
|
-
// is. Drop it for interactive mode so we launch the TUI (`codex` /
|
|
670
|
-
// `
|
|
671
|
-
//
|
|
696
|
+
// For Codex, Droid, and Muse, 'exec' is the headless subcommand; for OpenCode,
|
|
697
|
+
// 'run' is. Drop it for interactive mode so we launch the TUI (`codex` /
|
|
698
|
+
// `droid` / `muse` / `opencode`) instead of the one-shot headless subcommand
|
|
699
|
+
// ('codex exec' / 'droid exec' / 'muse exec' / 'opencode run').
|
|
672
700
|
if (interactive) {
|
|
673
|
-
if ((options.agent === 'codex' || options.agent === 'droid'
|
|
701
|
+
if ((options.agent === 'codex' || options.agent === 'droid' || options.agent === 'muse') &&
|
|
702
|
+
cmd[1] === 'exec') {
|
|
674
703
|
cmd.splice(1, 1);
|
|
675
704
|
}
|
|
676
705
|
else if (options.agent === 'opencode' && cmd[1] === 'run') {
|
|
@@ -682,8 +711,18 @@ export function buildExecCommand(options) {
|
|
|
682
711
|
// interactive drop above, `codex` -> `codex resume` (TUI). The session id is
|
|
683
712
|
// pushed later as the first positional (before any prompt). `{ flag }` agents
|
|
684
713
|
// (claude) need no base change — the flag is appended with the id below.
|
|
714
|
+
//
|
|
715
|
+
// Muse interactive resume is also a subcommand (`muse resume <id>`), but the
|
|
716
|
+
// session id MUST sit immediately after the verb — mode/model flags come
|
|
717
|
+
// after. So for interactive muse we push both `resume` and the id here, and
|
|
718
|
+
// skip the later generic resume flag path.
|
|
685
719
|
const resumeSpec = options.resume ? template.resume : undefined;
|
|
686
|
-
|
|
720
|
+
let museInteractiveResumeDone = false;
|
|
721
|
+
if (options.agent === 'muse' && options.resume && interactive && options.sessionId) {
|
|
722
|
+
cmd.push('resume', options.sessionId);
|
|
723
|
+
museInteractiveResumeDone = true;
|
|
724
|
+
}
|
|
725
|
+
else if (resumeSpec && 'subcommand' in resumeSpec) {
|
|
687
726
|
cmd.push(resumeSpec.subcommand);
|
|
688
727
|
}
|
|
689
728
|
// Use versioned alias if a specific version was requested (e.g., claude@2.1.98).
|
|
@@ -764,40 +803,26 @@ export function buildExecCommand(options) {
|
|
|
764
803
|
// narrower than --yolo/-f, which also bypasses permission checks.
|
|
765
804
|
cmd.push('--trust');
|
|
766
805
|
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
// inner writes fail with EROFS (e.g. `agents ssh` dies before connecting),
|
|
774
|
-
// which is why a remote `agents run codex` couldn't reach the fleet. Grant it
|
|
775
|
-
// implicitly whenever codex runs workspace-write — far narrower than --mode skip
|
|
776
|
-
// (danger-full-access). Fresh runs take --add-dir (below); resume forms reject
|
|
777
|
-
// --add-dir, so they take the same root via -c writable_roots here.
|
|
778
|
-
const codexWorkspaceWrite = options.agent === 'codex' && resolvedMode === 'edit';
|
|
779
|
-
if (resumeSpec && 'subcommand' in resumeSpec) {
|
|
806
|
+
if (options.agent === 'codex') {
|
|
807
|
+
const policyMode = resolvedMode === 'plan' || resolvedMode === 'skip' ? resolvedMode : 'edit';
|
|
808
|
+
const writableRoots = [...codexEditWritableRoots(), ...(options.addDirs ?? [])];
|
|
809
|
+
cmd.push(...codexPolicyArgs(policyMode, writableRoots));
|
|
810
|
+
}
|
|
811
|
+
else if (resumeSpec && 'subcommand' in resumeSpec) {
|
|
780
812
|
if (resolvedMode === 'skip') {
|
|
781
813
|
// skip = yolo on resume too; both `codex resume` (TUI) and
|
|
782
814
|
// `codex exec resume` accept the bypass flag.
|
|
783
815
|
cmd.push('--dangerously-bypass-approvals-and-sandbox');
|
|
784
816
|
}
|
|
785
817
|
else if (interactive) {
|
|
786
|
-
// `codex resume` (TUI) accepts the same -s/--sandbox flags as a fresh run.
|
|
787
818
|
cmd.push(...modeFlags);
|
|
788
|
-
if (codexWorkspaceWrite)
|
|
789
|
-
cmd.push('-c', codexWritableRootsConfig(getUserAgentsDir()));
|
|
790
819
|
}
|
|
791
820
|
else {
|
|
792
821
|
// `codex exec resume` rejects `--sandbox <mode>` (verified against
|
|
793
822
|
// `codex exec resume --help` on 0.142.5), but takes -c config overrides —
|
|
794
823
|
// map the mode through sandbox_mode so a non-skip resume never gets the
|
|
795
824
|
// approval/sandbox bypass.
|
|
796
|
-
cmd.push(
|
|
797
|
-
if (resolvedMode !== 'plan') {
|
|
798
|
-
cmd.push('-c', 'sandbox_workspace_write.network_access=true');
|
|
799
|
-
cmd.push('-c', codexWritableRootsConfig(getUserAgentsDir()));
|
|
800
|
-
}
|
|
825
|
+
cmd.push(...modeFlags);
|
|
801
826
|
}
|
|
802
827
|
}
|
|
803
828
|
else if (options.agent === 'kimi' && !interactive) {
|
|
@@ -835,7 +860,7 @@ export function buildExecCommand(options) {
|
|
|
835
860
|
// agents (codex) already pushed the verb above, so the id is the first
|
|
836
861
|
// positional here — placed before the prompt positional appended later. Without
|
|
837
862
|
// `resume`, the legacy claude-only `--session-id` CREATES a session with that id.
|
|
838
|
-
if (options.resume && options.sessionId && resumeSpec) {
|
|
863
|
+
if (options.resume && options.sessionId && resumeSpec && !museInteractiveResumeDone) {
|
|
839
864
|
if ('flag' in resumeSpec) {
|
|
840
865
|
const flag = interactive
|
|
841
866
|
? (resumeSpec.interactiveFlag ?? resumeSpec.flag)
|
|
@@ -913,21 +938,10 @@ export function buildExecCommand(options) {
|
|
|
913
938
|
cmd.push(template.promptFlag, options.prompt);
|
|
914
939
|
}
|
|
915
940
|
}
|
|
916
|
-
//
|
|
917
|
-
//
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
// the approval/sandbox bypass. Codex's resume forms reject --add-dir, so
|
|
921
|
-
// skip it there (claude's flag-based resume accepts it).
|
|
922
|
-
//
|
|
923
|
-
// On top of any user-supplied dirs, a fresh codex workspace-write run
|
|
924
|
-
// implicitly gets ~/.agents (deduped) so the CLI's own tooling — askpass,
|
|
925
|
-
// secrets, sessions, tunings — can write from inside the sandbox. Resume forms
|
|
926
|
-
// get the same root via -c writable_roots above (see codexWorkspaceWrite).
|
|
927
|
-
const codexImplicitDirs = codexWorkspaceWrite && !options.resume ? [getUserAgentsDir()] : [];
|
|
928
|
-
const addDirs = [...new Set([...(options.addDirs ?? []), ...codexImplicitDirs])];
|
|
929
|
-
if (addDirs.length > 0 &&
|
|
930
|
-
(options.agent === 'claude' || (options.agent === 'codex' && !options.resume))) {
|
|
941
|
+
// Codex add-dirs are folded into its named edit profile above, including on
|
|
942
|
+
// resume where the native CLI rejects --add-dir. Claude keeps its native flag.
|
|
943
|
+
const addDirs = [...new Set(options.addDirs ?? [])];
|
|
944
|
+
if (addDirs.length > 0 && options.agent === 'claude') {
|
|
931
945
|
for (const dir of addDirs) {
|
|
932
946
|
cmd.push('--add-dir', dir);
|
|
933
947
|
}
|
|
@@ -1024,8 +1038,11 @@ export async function execShimPassthrough(agent, rawArgs, cwd, pinnedVersion) {
|
|
|
1024
1038
|
if (fs.existsSync(cmdPath))
|
|
1025
1039
|
binary = cmdPath;
|
|
1026
1040
|
}
|
|
1027
|
-
//
|
|
1028
|
-
|
|
1041
|
+
// Match the POSIX shim: direct Codex launches default to the safe writable
|
|
1042
|
+
// profile, while later user arguments can still override native settings.
|
|
1043
|
+
const launchArgs = agent === 'codex'
|
|
1044
|
+
? ['-c', 'check_for_update_on_startup=false', ...codexPolicyArgs('edit')]
|
|
1045
|
+
: [];
|
|
1029
1046
|
// Mint a launch id and export it as AGENT_LAUNCH_ID so the agent's SessionStart
|
|
1030
1047
|
// hook records the same id — the join key that maps this launch to its exact
|
|
1031
1048
|
// session even though the recorded pid here is the cmd.exe wrapper (Windows) or
|
|
@@ -1192,14 +1209,28 @@ export function formatPaneTail(raw, maxLines = 30) {
|
|
|
1192
1209
|
* (Ctrl-b d) — return 0 and LEAVE the session for `agents focus` to re-attach.
|
|
1193
1210
|
*/
|
|
1194
1211
|
async function runInTmux(options, executable, args) {
|
|
1195
|
-
const { createSession, killSession, paneExitStatus, setSessionHook, slugifyName, agentPaneDiedHook, markSessionHookSchema } = await import('./tmux/session.js');
|
|
1212
|
+
const { createSession, hasSession, killSession, paneExitStatus, setSessionHook, slugifyName, agentPaneDiedHook, markSessionHookSchema } = await import('./tmux/session.js');
|
|
1196
1213
|
const { getDefaultSocketPath } = await import('./tmux/paths.js');
|
|
1197
1214
|
const { attachTmux, runTmux } = await import('./tmux/binary.js');
|
|
1198
1215
|
const socket = getDefaultSocketPath();
|
|
1199
1216
|
const cwd = options.cwd || process.cwd();
|
|
1200
1217
|
const idSeed = (options.sessionId ?? randomUUID()).slice(0, 8);
|
|
1201
1218
|
const name = slugifyName(`ag-${options.agent}-${idSeed}`);
|
|
1202
|
-
|
|
1219
|
+
// A native resume must not create a competing wrapper for a live session.
|
|
1220
|
+
// A retained dead pane is reaped before the harness resumes normally.
|
|
1221
|
+
if (options.resume && await hasSession(name, socket)) {
|
|
1222
|
+
const existing = await createSession({ name, cwd, socket, source: 'cli', attachExisting: true });
|
|
1223
|
+
if (options.sessionId)
|
|
1224
|
+
writeSessionAliasRecord(options.sessionId, name);
|
|
1225
|
+
if (!existing.pane || !(await paneExitStatus(existing.pane, socket)).dead) {
|
|
1226
|
+
await attachTmux({ socket, args: ['attach-session', '-t', name] });
|
|
1227
|
+
return { exitCode: 0, stderr: '', stdout: '' };
|
|
1228
|
+
}
|
|
1229
|
+
await killSession(name, socket);
|
|
1230
|
+
}
|
|
1231
|
+
// SessionStart learns some harness IDs only after launch. Carry the wrapper
|
|
1232
|
+
// name into that hook so it can bind both identities durably.
|
|
1233
|
+
const execEnv = { ...buildExecEnv(options), AGENT_TMUX_SESSION_NAME: name };
|
|
1203
1234
|
// Launch with the real env (secret VALUES materialized into the pane); persist
|
|
1204
1235
|
// only a value-redacted copy in SessionMeta.cmd so resolved secrets never hit
|
|
1205
1236
|
// disk via the informational cmd field (RUSH-1758).
|
|
@@ -1210,6 +1241,8 @@ async function runInTmux(options, executable, args) {
|
|
|
1210
1241
|
labels.sessionId = options.sessionId;
|
|
1211
1242
|
const meta = await createSession({ name, cmd, metaCmd, cwd, socket, source: 'cli', labels });
|
|
1212
1243
|
const pane = meta.pane;
|
|
1244
|
+
if (options.sessionId)
|
|
1245
|
+
writeSessionAliasRecord(options.sessionId, name);
|
|
1213
1246
|
if (pane) {
|
|
1214
1247
|
// When the AGENT pane dies, detach the client (don't kill) so the session
|
|
1215
1248
|
// survives just long enough to read the dead pane's exit status below. The
|
|
@@ -1873,6 +1906,7 @@ export async function runWithFallback(options) {
|
|
|
1873
1906
|
...options,
|
|
1874
1907
|
agent,
|
|
1875
1908
|
version,
|
|
1909
|
+
mode: options.modeWasImplicit ? implicitModeFor(agent) : options.mode,
|
|
1876
1910
|
prompt,
|
|
1877
1911
|
env: envOverride ? { ...(options.env ?? {}), ...envOverride } : options.env,
|
|
1878
1912
|
sessionId: pinnedSessionId ?? (i === 0 ? options.sessionId : undefined),
|
|
@@ -141,26 +141,7 @@ export declare function scrubOutboundDashes(text: string): string;
|
|
|
141
141
|
* host last. Skip the uninformative default label `agent`.
|
|
142
142
|
*/
|
|
143
143
|
export declare function composeBroadcastFooter(ctx: FeedBroadcastContext): string | undefined;
|
|
144
|
-
|
|
145
|
-
* Human-facing body for a messaging sink (`{message}`).
|
|
146
|
-
*
|
|
147
|
-
* ```
|
|
148
|
-
* Title in a few words
|
|
149
|
-
*
|
|
150
|
-
* Body of what happened or the ask.
|
|
151
|
-
* Options: publish / wait (blocks with choices)
|
|
152
|
-
* Default in 15 min: wait (blocks with a safe default)
|
|
153
|
-
*
|
|
154
|
-
* Sent from grok/a02da0e2 on mac-mini
|
|
155
|
-
* https://… (optional attach URL)
|
|
156
|
-
* ```
|
|
157
|
-
*
|
|
158
|
-
* Title first (scannable subject). Blank line. Body. Then the phone-actionable
|
|
159
|
-
* choices + default (a block that has stopped for the human), then footer
|
|
160
|
-
* provenance. No `agents focus <id>` line: a CLI command is unusable from a phone,
|
|
161
|
-
* so the safe default is the fallback and the message carries it. Prefer `{message}`
|
|
162
|
-
* over bare `{text}` in messaging sinks.
|
|
163
|
-
*/
|
|
144
|
+
export declare function truncateBroadcastBody(body: string): string;
|
|
164
145
|
export declare function composeBroadcastMessage(ctx: FeedBroadcastContext): string;
|
|
165
146
|
/**
|
|
166
147
|
* Substitute `{placeholder}` tokens in an argv template. Returns undefined when
|
|
@@ -202,9 +202,39 @@ export function composeBroadcastFooter(ctx) {
|
|
|
202
202
|
* so the safe default is the fallback and the message carries it. Prefer `{message}`
|
|
203
203
|
* over bare `{text}` in messaging sinks.
|
|
204
204
|
*/
|
|
205
|
+
/**
|
|
206
|
+
* The phone copy is a text, not a report. A long body reaches the owner's phone
|
|
207
|
+
* as an unreadable wall, and the `feed post` forwarding path is where that has to
|
|
208
|
+
* be shaped: every sink (owner alias, in-process `channel:`, spawned `command:`)
|
|
209
|
+
* funnels through {@link composeBroadcastMessage}, which is the only seam a shell
|
|
210
|
+
* hook cannot reach. Keep the title (the scannable headline) and cap the BODY to a
|
|
211
|
+
* short excerpt, marking the cut with a plain "(full in feed)" — NOT a CLI command
|
|
212
|
+
* (unusable from a phone, see the note on composeBroadcastMessage). Nothing is
|
|
213
|
+
* lost: this shapes only the outbound sink text; the full post stays in the feed.
|
|
214
|
+
*/
|
|
215
|
+
const PHONE_BODY_MAX_CHARS = 500;
|
|
216
|
+
const PHONE_BODY_MAX_LINES = 8;
|
|
217
|
+
export function truncateBroadcastBody(body) {
|
|
218
|
+
if (!body)
|
|
219
|
+
return body;
|
|
220
|
+
let out = body;
|
|
221
|
+
let cut = false;
|
|
222
|
+
const lines = out.split('\n');
|
|
223
|
+
if (lines.length > PHONE_BODY_MAX_LINES) {
|
|
224
|
+
out = lines.slice(0, PHONE_BODY_MAX_LINES).join('\n');
|
|
225
|
+
cut = true;
|
|
226
|
+
}
|
|
227
|
+
if (out.length > PHONE_BODY_MAX_CHARS) {
|
|
228
|
+
out = out.slice(0, PHONE_BODY_MAX_CHARS);
|
|
229
|
+
cut = true;
|
|
230
|
+
}
|
|
231
|
+
if (!cut)
|
|
232
|
+
return body;
|
|
233
|
+
return `${out.trimEnd()}\n… (full in feed)`;
|
|
234
|
+
}
|
|
205
235
|
export function composeBroadcastMessage(ctx) {
|
|
206
236
|
const title = scrubOutboundDashes(ctx.title ?? '');
|
|
207
|
-
const body = scrubOutboundDashes(ctx.text ?? '');
|
|
237
|
+
const body = truncateBroadcastBody(scrubOutboundDashes(ctx.text ?? ''));
|
|
208
238
|
// Title preferred; if an older post has no title, body alone still sends.
|
|
209
239
|
const head = title || body;
|
|
210
240
|
const mid = title && body && title !== body ? body : undefined;
|
package/dist/lib/hooks.js
CHANGED
|
@@ -758,7 +758,7 @@ export function listHooksInVersionHome(agent, version) {
|
|
|
758
758
|
* map (Gemini/Antigravity settings.json variants, Codex config.toml, the OpenCode
|
|
759
759
|
* plugin, …), so it reports them unsupported rather than risk a false verdict.
|
|
760
760
|
*/
|
|
761
|
-
const SETTINGS_JSON_HOOK_FAMILY = ['claude', 'droid'];
|
|
761
|
+
const SETTINGS_JSON_HOOK_FAMILY = ['claude', 'droid', 'muse'];
|
|
762
762
|
/**
|
|
763
763
|
* Verify that every hook the manifest says should be wired for a (claude|droid)
|
|
764
764
|
* version is actually REFERENCED in that version's native settings.json — not
|
|
@@ -1453,6 +1453,12 @@ export function registerHooksToSettings(agentId, versionHome, hookManifest, agen
|
|
|
1453
1453
|
// registrar targeting `.factory/settings.json` (agentConfigDirName('droid')).
|
|
1454
1454
|
return registerHooksForClaude(versionHome, manifest, resolveScript, managedPrefixes, agentConfigDirName('droid'));
|
|
1455
1455
|
}
|
|
1456
|
+
if (agentId === 'muse') {
|
|
1457
|
+
// Muse Code: Claude-compatible hooks block in ~/.config/muse/settings.json
|
|
1458
|
+
// (events SessionStart / PreToolUse / …, matcher groups, command hooks).
|
|
1459
|
+
// settings.json MUST carry schema_version: 1 or Muse refuses to start.
|
|
1460
|
+
return registerHooksForClaude(versionHome, manifest, resolveScript, managedPrefixes, agentConfigDirName('muse'), { schemaVersion: 1 });
|
|
1461
|
+
}
|
|
1456
1462
|
if (agentId === 'codex') {
|
|
1457
1463
|
return registerHooksForCodex(versionHome, manifest, resolveScript, managedPrefixes);
|
|
1458
1464
|
}
|
|
@@ -1675,7 +1681,7 @@ const ANTIGRAVITY_EVENT_MAP = {
|
|
|
1675
1681
|
Stop: 'on_loop_stop',
|
|
1676
1682
|
OnError: 'on_error',
|
|
1677
1683
|
};
|
|
1678
|
-
function registerHooksForClaude(versionHome, manifest, resolveScript, managedPrefixes, configDirName = '.claude') {
|
|
1684
|
+
function registerHooksForClaude(versionHome, manifest, resolveScript, managedPrefixes, configDirName = '.claude', options) {
|
|
1679
1685
|
const registered = [];
|
|
1680
1686
|
const errors = [];
|
|
1681
1687
|
const configDir = path.join(versionHome, configDirName);
|
|
@@ -1692,6 +1698,10 @@ function registerHooksForClaude(versionHome, manifest, resolveScript, managedPre
|
|
|
1692
1698
|
return { registered, errors };
|
|
1693
1699
|
}
|
|
1694
1700
|
}
|
|
1701
|
+
// Muse (and any future agent that pins a settings schema) requires this key.
|
|
1702
|
+
if (options?.schemaVersion !== undefined && config.schema_version === undefined) {
|
|
1703
|
+
config.schema_version = options.schemaVersion;
|
|
1704
|
+
}
|
|
1695
1705
|
if (!config.hooks || typeof config.hooks !== 'object') {
|
|
1696
1706
|
config.hooks = {};
|
|
1697
1707
|
}
|
package/dist/lib/mcp.js
CHANGED
|
@@ -595,6 +595,7 @@ function writeMcpConfigSupportsAgent(agentId) {
|
|
|
595
595
|
case 'opencode':
|
|
596
596
|
case 'hermes':
|
|
597
597
|
case 'pi':
|
|
598
|
+
case 'muse':
|
|
598
599
|
return true;
|
|
599
600
|
default:
|
|
600
601
|
return false;
|
|
@@ -797,6 +798,49 @@ export function writeMcpConfig(agentId, configPath, servers, mode = 'overwrite')
|
|
|
797
798
|
fs.writeFileSync(configPath, yaml.stringify(config), 'utf-8');
|
|
798
799
|
break;
|
|
799
800
|
}
|
|
801
|
+
case 'muse': {
|
|
802
|
+
// Muse Code settings.json: requires schema_version: 1; MCP lives under
|
|
803
|
+
// mcp_servers with an explicit transport (stdio | streamable_http).
|
|
804
|
+
// See https://dev.meta.ai/docs/muse-code/extending#mcp
|
|
805
|
+
let config = {};
|
|
806
|
+
if (fs.existsSync(configPath)) {
|
|
807
|
+
try {
|
|
808
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
809
|
+
}
|
|
810
|
+
catch {
|
|
811
|
+
config = {};
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
if (config.schema_version === undefined) {
|
|
815
|
+
config.schema_version = 1;
|
|
816
|
+
}
|
|
817
|
+
const mcpServers = mode === 'merge' && config.mcp_servers && typeof config.mcp_servers === 'object' && !Array.isArray(config.mcp_servers)
|
|
818
|
+
? { ...config.mcp_servers }
|
|
819
|
+
: {};
|
|
820
|
+
for (const server of servers) {
|
|
821
|
+
if (server.transport === 'stdio') {
|
|
822
|
+
mcpServers[server.name] = {
|
|
823
|
+
transport: 'stdio',
|
|
824
|
+
command: server.command,
|
|
825
|
+
args: server.args || [],
|
|
826
|
+
...(server.env && Object.keys(server.env).length > 0 ? { env: server.env } : {}),
|
|
827
|
+
enabled: true,
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
else {
|
|
831
|
+
mcpServers[server.name] = {
|
|
832
|
+
transport: 'streamable_http',
|
|
833
|
+
url: server.url,
|
|
834
|
+
...(server.headers && Object.keys(server.headers).length > 0 ? { headers: server.headers } : {}),
|
|
835
|
+
enabled: true,
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
config.mcp_servers = mcpServers;
|
|
840
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
841
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
842
|
+
break;
|
|
843
|
+
}
|
|
800
844
|
}
|
|
801
845
|
}
|
|
802
846
|
/**
|
|
@@ -7,11 +7,7 @@
|
|
|
7
7
|
<key>CFBundleIdentifier</key>
|
|
8
8
|
<string>com.phnx-labs.agents-menubar</string>
|
|
9
9
|
<key>CFBundleName</key>
|
|
10
|
-
<string>
|
|
11
|
-
<key>CFBundleDisplayName</key>
|
|
12
|
-
<string>AGI Menu</string>
|
|
13
|
-
<key>CFBundleIconFile</key>
|
|
14
|
-
<string>AppIcon</string>
|
|
10
|
+
<string>Agents Menu Bar</string>
|
|
15
11
|
<key>CFBundlePackageType</key>
|
|
16
12
|
<string>APPL</string>
|
|
17
13
|
<key>CFBundleShortVersionString</key>
|
|
Binary file
|
|
@@ -3,22 +3,9 @@
|
|
|
3
3
|
<plist version="1.0">
|
|
4
4
|
<dict>
|
|
5
5
|
<key>files</key>
|
|
6
|
-
<dict
|
|
7
|
-
<key>Resources/AppIcon.icns</key>
|
|
8
|
-
<data>
|
|
9
|
-
jOjZVimcFRHoP2VPzgn8uM8mjkA=
|
|
10
|
-
</data>
|
|
11
|
-
</dict>
|
|
6
|
+
<dict/>
|
|
12
7
|
<key>files2</key>
|
|
13
|
-
<dict
|
|
14
|
-
<key>Resources/AppIcon.icns</key>
|
|
15
|
-
<dict>
|
|
16
|
-
<key>hash2</key>
|
|
17
|
-
<data>
|
|
18
|
-
GFvSLeNYJ3ASxW3OzcuiB4aID0gZUBkpozmWZkbTFNw=
|
|
19
|
-
</data>
|
|
20
|
-
</dict>
|
|
21
|
-
</dict>
|
|
8
|
+
<dict/>
|
|
22
9
|
<key>rules</key>
|
|
23
10
|
<dict>
|
|
24
11
|
<key>^Resources/</key>
|
package/dist/lib/models.d.ts
CHANGED
|
@@ -97,11 +97,6 @@ export declare function parseGrokModelsStdout(stdout: string): {
|
|
|
97
97
|
models: ModelInfo[];
|
|
98
98
|
aliases: Record<string, string>;
|
|
99
99
|
};
|
|
100
|
-
/**
|
|
101
|
-
* Build (or load from cache) the model catalog for a specific (agent, version).
|
|
102
|
-
* Cache is keyed on source-file mtime (binary or js module), so re-extracts
|
|
103
|
-
* automatically when the user upgrades or reinstalls a version.
|
|
104
|
-
*/
|
|
105
100
|
export declare function getModelCatalog(agent: AgentId, version: string): ModelCatalog | null;
|
|
106
101
|
/** The result of resolving a user-supplied model string against the catalog. */
|
|
107
102
|
export interface ResolvedModel {
|
package/dist/lib/models.js
CHANGED
|
@@ -246,6 +246,16 @@ export function locateModelSource(agent, version) {
|
|
|
246
246
|
return { path: pathBin, kind: 'cli' };
|
|
247
247
|
return null;
|
|
248
248
|
}
|
|
249
|
+
if (agent === 'muse') {
|
|
250
|
+
// Muse Code is a self-updating native binary on PATH. It has no catalog
|
|
251
|
+
// CLI; the published model IDs are fixed by Meta Model API docs. Point at
|
|
252
|
+
// the binary so the cache key moves with upgrades, and extract a static
|
|
253
|
+
// catalog in getModelCatalog.
|
|
254
|
+
const pathBin = findOnPath('muse');
|
|
255
|
+
if (pathBin)
|
|
256
|
+
return { path: pathBin, kind: 'cli' };
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
249
259
|
return null;
|
|
250
260
|
}
|
|
251
261
|
/** Real Grok binaries are ~100MB+; failed-download stubs are tens of bytes. */
|
|
@@ -973,6 +983,36 @@ function extractPiCatalog(binaryPath) {
|
|
|
973
983
|
* Cache is keyed on source-file mtime (binary or js module), so re-extracts
|
|
974
984
|
* automatically when the user upgrades or reinstalls a version.
|
|
975
985
|
*/
|
|
986
|
+
/**
|
|
987
|
+
* Static Muse Spark catalog. Meta Model API publishes these IDs; Muse Code
|
|
988
|
+
* has no `muse models` command. Default is muse-spark-1.2 (docs + first-run).
|
|
989
|
+
* Source: https://dev.meta.ai/docs/pricing-rate-limits and muse-code overview.
|
|
990
|
+
*/
|
|
991
|
+
function extractMuseCatalog() {
|
|
992
|
+
const models = [
|
|
993
|
+
{
|
|
994
|
+
id: 'muse-spark-1.2',
|
|
995
|
+
displayName: 'Muse Spark 1.2',
|
|
996
|
+
isDefault: true,
|
|
997
|
+
},
|
|
998
|
+
{
|
|
999
|
+
id: 'muse-spark-1.1',
|
|
1000
|
+
displayName: 'Muse Spark 1.1',
|
|
1001
|
+
},
|
|
1002
|
+
{
|
|
1003
|
+
id: 'muse-spark-1.2-contributor',
|
|
1004
|
+
displayName: 'Muse Spark 1.2 Contributor',
|
|
1005
|
+
},
|
|
1006
|
+
];
|
|
1007
|
+
const aliases = {
|
|
1008
|
+
spark: 'muse-spark-1.2',
|
|
1009
|
+
'spark-1.2': 'muse-spark-1.2',
|
|
1010
|
+
'spark-1.1': 'muse-spark-1.1',
|
|
1011
|
+
contributor: 'muse-spark-1.2-contributor',
|
|
1012
|
+
default: 'muse-spark-1.2',
|
|
1013
|
+
};
|
|
1014
|
+
return { models, aliases };
|
|
1015
|
+
}
|
|
976
1016
|
export function getModelCatalog(agent, version) {
|
|
977
1017
|
const src = locateModelSource(agent, version);
|
|
978
1018
|
if (!src)
|
|
@@ -1027,6 +1067,8 @@ export function getModelCatalog(agent, version) {
|
|
|
1027
1067
|
({ models, aliases } = extractGrokCatalog(src.path));
|
|
1028
1068
|
else if (agent === 'pi')
|
|
1029
1069
|
({ models, aliases } = extractPiCatalog(src.path));
|
|
1070
|
+
else if (agent === 'muse')
|
|
1071
|
+
({ models, aliases } = extractMuseCatalog());
|
|
1030
1072
|
}
|
|
1031
1073
|
// Attach per-token pricing where the offline table knows the model, so the
|
|
1032
1074
|
// catalog carries $/token for the tier display and budgeting. Subscription /
|
|
@@ -1237,5 +1279,11 @@ export function buildReasoningFlags(agent, level) {
|
|
|
1237
1279
|
const grokLevel = (normalized === 'xhigh' || normalized === 'max') ? 'high' : normalized;
|
|
1238
1280
|
return ['--reasoning-effort', grokLevel];
|
|
1239
1281
|
}
|
|
1282
|
+
if (agent === 'muse') {
|
|
1283
|
+
// Muse Code: `--reasoning-effort none|minimal|low|medium|high|xhigh|ultra`.
|
|
1284
|
+
// Map our unified `max` to Muse's `ultra` (client-side multi-agent aggression).
|
|
1285
|
+
const museLevel = normalized === 'max' ? 'ultra' : normalized;
|
|
1286
|
+
return ['--reasoning-effort', museLevel];
|
|
1287
|
+
}
|
|
1240
1288
|
return [];
|
|
1241
1289
|
}
|
|
@@ -121,6 +121,11 @@ function dirExists(p) {
|
|
|
121
121
|
}
|
|
122
122
|
// ─── Per-version paths ────────────────────────────────────────────────────────
|
|
123
123
|
function pluginsRootForVersion(agent, versionHome) {
|
|
124
|
+
// Muse Code keeps its plugin store under XDG data (`~/.local/share/muse/plugins`),
|
|
125
|
+
// not under the config dir. Under HOME isolation the version home is $HOME.
|
|
126
|
+
if (agent === 'muse') {
|
|
127
|
+
return path.join(versionHome, '.local', 'share', 'muse', 'plugins');
|
|
128
|
+
}
|
|
124
129
|
return path.join(versionHome, agentConfigDirName(agent), 'plugins');
|
|
125
130
|
}
|
|
126
131
|
export function marketplaceRoot(specOrName, agent, versionHome) {
|
|
@@ -678,6 +683,10 @@ export function addPluginToSettings(pluginName, marketplaceName, agent, versionH
|
|
|
678
683
|
settings = {};
|
|
679
684
|
}
|
|
680
685
|
}
|
|
686
|
+
// Muse refuses to start without schema_version: 1.
|
|
687
|
+
if (agent === 'muse' && settings.schema_version === undefined) {
|
|
688
|
+
settings.schema_version = 1;
|
|
689
|
+
}
|
|
681
690
|
if (!settings.enabledPlugins || typeof settings.enabledPlugins !== 'object') {
|
|
682
691
|
settings.enabledPlugins = {};
|
|
683
692
|
}
|