@modootoday/pmcp 0.14.1 → 0.16.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.
- package/.agents/plugins/marketplace.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +57 -8
- package/dist/cli.js +1514 -589
- package/dist/harness-launch-gate.js +0 -3
- package/dist/harness-worker.js +0 -4
- package/dist/http.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +87 -25
- package/dist/mailbox/index.js +0 -3
- package/dist/stdio.js +1 -3
- package/dist/tui-worker.js +580 -32
- package/docs/skills.md +3 -3
- package/docs/terminal-workspace.md +161 -1
- package/gemini-extension.json +1 -1
- package/package.json +3 -2
- package/plugin.json +1 -1
- package/plugins/pmcp/.claude-plugin/plugin.json +1 -1
- package/plugins/pmcp/gemini-extension.json +1 -1
- package/plugins/pmcp/plugin.json +1 -1
package/dist/tui-worker.js
CHANGED
|
@@ -64,6 +64,11 @@ var tuiOptions = [
|
|
|
64
64
|
},
|
|
65
65
|
{ name: "rows", describe: "Terminal rows (12\u20131000)", placeholder: "<count>" },
|
|
66
66
|
{ name: "index", describe: "Worker dock index", placeholder: "<count>" },
|
|
67
|
+
{
|
|
68
|
+
name: "memory-mb",
|
|
69
|
+
describe: "Worker memory reservation",
|
|
70
|
+
placeholder: "<count>"
|
|
71
|
+
},
|
|
67
72
|
{
|
|
68
73
|
name: "confirm-session",
|
|
69
74
|
describe: "Exact target ID for stop",
|
|
@@ -71,9 +76,24 @@ var tuiOptions = [
|
|
|
71
76
|
},
|
|
72
77
|
{
|
|
73
78
|
name: "config",
|
|
74
|
-
describe: "
|
|
79
|
+
describe: "Project pmcp.toml (or mailbox config for create)",
|
|
75
80
|
placeholder: "<file>"
|
|
76
81
|
},
|
|
82
|
+
{
|
|
83
|
+
name: "runtime",
|
|
84
|
+
describe: "Native runtime ID for main launch or worker start",
|
|
85
|
+
placeholder: "<runtime>"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: "yes",
|
|
89
|
+
describe: "Confirm the first native runtime start",
|
|
90
|
+
boolean: true
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "new",
|
|
94
|
+
describe: "Create a workspace after the previous group is stopped",
|
|
95
|
+
boolean: true
|
|
96
|
+
},
|
|
77
97
|
{
|
|
78
98
|
name: "actor-file",
|
|
79
99
|
describe: "Optional private mailbox observer credential",
|
|
@@ -81,6 +101,10 @@ var tuiOptions = [
|
|
|
81
101
|
}
|
|
82
102
|
];
|
|
83
103
|
var allowed = {
|
|
104
|
+
launch: ["config", "runtime", "yes", "new"],
|
|
105
|
+
plan: ["config", "runtime"],
|
|
106
|
+
recovery: ["config"],
|
|
107
|
+
"start-worker": ["config", "view", "runtime", "memory-mb"],
|
|
84
108
|
doctor: [],
|
|
85
109
|
create: ["group-file", "columns", "rows", "config", "actor-file"],
|
|
86
110
|
inspect: ["view"],
|
|
@@ -98,10 +122,12 @@ var allowed = {
|
|
|
98
122
|
close: ["view"],
|
|
99
123
|
dismiss: ["view"],
|
|
100
124
|
management: ["view"],
|
|
125
|
+
"worker-menu": ["view"],
|
|
101
126
|
check: ["view"]
|
|
102
127
|
};
|
|
103
128
|
function parseTui(args) {
|
|
104
|
-
|
|
129
|
+
let [action, ...extra] = args.positional;
|
|
130
|
+
if (action === void 0) action = one(args, "view") ? "open" : "launch";
|
|
105
131
|
const options = action ? allowed[action] : void 0;
|
|
106
132
|
if (!action || !options || extra.length)
|
|
107
133
|
throw new ArgumentError("Provide one supported TUI operation");
|
|
@@ -121,6 +147,33 @@ function parseTui(args) {
|
|
|
121
147
|
return Number(value);
|
|
122
148
|
};
|
|
123
149
|
if (action === "doctor") return { action, input: {} };
|
|
150
|
+
if (action === "start-worker") {
|
|
151
|
+
const config = one(args, "config");
|
|
152
|
+
const viewFile = one(args, "view");
|
|
153
|
+
if (config && viewFile)
|
|
154
|
+
throw new ArgumentError("Choose --config or --view for worker start");
|
|
155
|
+
return {
|
|
156
|
+
action,
|
|
157
|
+
viewFile,
|
|
158
|
+
input: {},
|
|
159
|
+
startup: {
|
|
160
|
+
config,
|
|
161
|
+
runtime: required("runtime"),
|
|
162
|
+
memoryMb: number("memory-mb")
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (action === "launch" || action === "plan" || action === "recovery")
|
|
167
|
+
return {
|
|
168
|
+
action,
|
|
169
|
+
input: {},
|
|
170
|
+
startup: {
|
|
171
|
+
config: one(args, "config"),
|
|
172
|
+
runtime: one(args, "runtime"),
|
|
173
|
+
fresh: args.flags.has("new"),
|
|
174
|
+
confirmed: args.flags.has("yes")
|
|
175
|
+
}
|
|
176
|
+
};
|
|
124
177
|
if (action === "create") {
|
|
125
178
|
const config = one(args, "config");
|
|
126
179
|
const actorFile = one(args, "actor-file");
|
|
@@ -677,9 +730,9 @@ async function attachNative(spec, renew) {
|
|
|
677
730
|
}, 2e4) : void 0;
|
|
678
731
|
let exitCode;
|
|
679
732
|
try {
|
|
680
|
-
exitCode = await new Promise((
|
|
733
|
+
exitCode = await new Promise((resolve5, reject) => {
|
|
681
734
|
client.once("error", reject);
|
|
682
|
-
client.once("exit", (code) =>
|
|
735
|
+
client.once("exit", (code) => resolve5(code ?? 1));
|
|
683
736
|
});
|
|
684
737
|
} finally {
|
|
685
738
|
if (timer) clearInterval(timer);
|
|
@@ -858,7 +911,6 @@ var Connection = class {
|
|
|
858
911
|
constructor(records2) {
|
|
859
912
|
this.records = records2;
|
|
860
913
|
}
|
|
861
|
-
records;
|
|
862
914
|
tmux(args, options = {}) {
|
|
863
915
|
return command(
|
|
864
916
|
"tmux",
|
|
@@ -1095,8 +1147,6 @@ var LaunchJournal = class {
|
|
|
1095
1147
|
this.id = id2;
|
|
1096
1148
|
runtimeUnit(id2);
|
|
1097
1149
|
}
|
|
1098
|
-
records;
|
|
1099
|
-
id;
|
|
1100
1150
|
path(kind) {
|
|
1101
1151
|
if (!["intent", "proof", "grant", "committed", "aborted"].includes(kind))
|
|
1102
1152
|
throw new Error("invalid_launch_artifact");
|
|
@@ -1594,7 +1644,7 @@ var DockerEngine = class {
|
|
|
1594
1644
|
if (!/^1\.\d+$/.test(version))
|
|
1595
1645
|
throw new Error("invalid_docker_api_version");
|
|
1596
1646
|
const payload = JSON.stringify(configuration);
|
|
1597
|
-
return new Promise((
|
|
1647
|
+
return new Promise((resolve5, reject) => {
|
|
1598
1648
|
const req = request(
|
|
1599
1649
|
{
|
|
1600
1650
|
socketPath: new URL(endpoint).pathname,
|
|
@@ -1629,7 +1679,7 @@ var DockerEngine = class {
|
|
|
1629
1679
|
);
|
|
1630
1680
|
if (!/^[a-f0-9]{64}$/.test(id2))
|
|
1631
1681
|
throw new Error("invalid_container_id");
|
|
1632
|
-
|
|
1682
|
+
resolve5(id2);
|
|
1633
1683
|
} catch (error) {
|
|
1634
1684
|
reject(error);
|
|
1635
1685
|
}
|
|
@@ -2225,7 +2275,6 @@ var Backend = class {
|
|
|
2225
2275
|
this.runDirectory = recordStore.directory;
|
|
2226
2276
|
this.metadataPath = recordStore.metadataPath;
|
|
2227
2277
|
}
|
|
2228
|
-
recordStore;
|
|
2229
2278
|
connection;
|
|
2230
2279
|
owner;
|
|
2231
2280
|
socket;
|
|
@@ -2709,7 +2758,7 @@ async function watchSession(operation) {
|
|
|
2709
2758
|
)
|
|
2710
2759
|
});
|
|
2711
2760
|
if (index + 1 < count)
|
|
2712
|
-
await new Promise((
|
|
2761
|
+
await new Promise((resolve5) => setTimeout(resolve5, intervalMs));
|
|
2713
2762
|
}
|
|
2714
2763
|
return {
|
|
2715
2764
|
schemaVersion: 1,
|
|
@@ -3364,7 +3413,6 @@ var CommonHarness = class {
|
|
|
3364
3413
|
constructor(view) {
|
|
3365
3414
|
this.view = view;
|
|
3366
3415
|
}
|
|
3367
|
-
view;
|
|
3368
3416
|
async call(family, action, input = {}) {
|
|
3369
3417
|
const group = new GroupStore(this.view.groupFile).state.group;
|
|
3370
3418
|
if (group.owner !== this.view.groupOwner) throw new Error("foreign_group");
|
|
@@ -3398,6 +3446,19 @@ var CommonHarness = class {
|
|
|
3398
3446
|
// src/tui/application/feedback.ts
|
|
3399
3447
|
import { join as join20 } from "path";
|
|
3400
3448
|
var hints = {
|
|
3449
|
+
startup_requires_recovery: "Run pmcp tui recovery for saved ownership and exact inspection commands. No runtime was restarted.",
|
|
3450
|
+
startup_group_paused: "The saved group is paused. Resume it explicitly through the harness CLI before reopening.",
|
|
3451
|
+
startup_main_unavailable: "Inspect the saved main and recovery state. Stop the old group explicitly before using pmcp tui --new.",
|
|
3452
|
+
startup_busy: "Another workspace launch is running. Wait and retry.",
|
|
3453
|
+
startup_runtime_conflict: "The saved main uses another runtime. Stop the old group before creating a new workspace.",
|
|
3454
|
+
workspace_already_running: "Reopen the existing workspace, or stop its exact owned group before using --new.",
|
|
3455
|
+
runtime_executable_unavailable: "Install the selected native CLI on PATH, or choose an installed runtime with --runtime.",
|
|
3456
|
+
view_client_unavailable: "Open the view before using the worker menu, or run pmcp tui start-worker with --view and --runtime.",
|
|
3457
|
+
runtime_not_granted: "Choose a runtime granted by this owned group. Inspect the group before creating another workspace.",
|
|
3458
|
+
runtime_capacity_exceeded: "Stop an exact owned worker before adding another, or use the main session alone.",
|
|
3459
|
+
aggregate_memory_budget_exceeded: "Use fewer sessions or start a worker with an explicit smaller --memory-mb reservation.",
|
|
3460
|
+
group_not_ready: "Inspect the group and resume a paused group explicitly before adding a worker.",
|
|
3461
|
+
startup_group_not_ready: "Inspect the group and resume a paused group explicitly before adding a worker.",
|
|
3401
3462
|
control_connection_lost: "The native writer or lease disappeared. Inspect and release control before reconnecting.",
|
|
3402
3463
|
control_busy: "Another controller holds the lease. Release it explicitly before retrying.",
|
|
3403
3464
|
native_writer_active: "A native writer is still attached. Detach that writer before retrying.",
|
|
@@ -3453,6 +3514,14 @@ function phaseLabel(phase2) {
|
|
|
3453
3514
|
return labels[phase2];
|
|
3454
3515
|
}
|
|
3455
3516
|
|
|
3517
|
+
// src/tui/presentation/target.ts
|
|
3518
|
+
function targetStatus(view) {
|
|
3519
|
+
const role2 = view.target === view.mainId ? "MAIN" : "WORKER";
|
|
3520
|
+
const phase2 = view.phase === "read-only" ? "OBSERVE" : phaseLabel(view.phase);
|
|
3521
|
+
const input = view.phase === "controlled" ? "INPUT ON" : "INPUT OFF";
|
|
3522
|
+
return `${role2} ${view.target.slice(0, 8)} | ${phase2} | ${input}`;
|
|
3523
|
+
}
|
|
3524
|
+
|
|
3456
3525
|
// src/tui/adapters/tmux/connection.ts
|
|
3457
3526
|
import { spawn as spawn2 } from "child_process";
|
|
3458
3527
|
import { join as join21 } from "path";
|
|
@@ -3501,9 +3570,6 @@ var ViewConnection = class {
|
|
|
3501
3570
|
this.identity = identity;
|
|
3502
3571
|
this.socket = join21(directory, "s");
|
|
3503
3572
|
}
|
|
3504
|
-
directory;
|
|
3505
|
-
owner;
|
|
3506
|
-
identity;
|
|
3507
3573
|
socket;
|
|
3508
3574
|
raw(args) {
|
|
3509
3575
|
const env = { ...process.env };
|
|
@@ -3573,9 +3639,9 @@ var ViewConnection = class {
|
|
|
3573
3639
|
["-S", this.socket, "attach-session", "-E", "-t", view.session],
|
|
3574
3640
|
{ stdio: "inherit", env }
|
|
3575
3641
|
);
|
|
3576
|
-
return new Promise((
|
|
3642
|
+
return new Promise((resolve5, reject) => {
|
|
3577
3643
|
client.once("error", reject);
|
|
3578
|
-
client.once("exit", (code) =>
|
|
3644
|
+
client.once("exit", (code) => resolve5(code ?? 1));
|
|
3579
3645
|
});
|
|
3580
3646
|
}
|
|
3581
3647
|
};
|
|
@@ -3606,7 +3672,8 @@ function bindKeys(connection, view, file) {
|
|
|
3606
3672
|
["b", "mailbox"],
|
|
3607
3673
|
["d", "detach"],
|
|
3608
3674
|
["!", "dismiss"],
|
|
3609
|
-
["g", "management"]
|
|
3675
|
+
["g", "management"],
|
|
3676
|
+
["n", "worker-menu"]
|
|
3610
3677
|
])
|
|
3611
3678
|
connection.command([
|
|
3612
3679
|
"bind-key",
|
|
@@ -3627,7 +3694,7 @@ function bindKeys(connection, view, file) {
|
|
|
3627
3694
|
"bind-key",
|
|
3628
3695
|
"?",
|
|
3629
3696
|
"display-message",
|
|
3630
|
-
"m main; w/1-4 worker; c control; r release; h history; b mailbox; d detach; ! dismiss error; double Ctrl-g forwards native Ctrl-g"
|
|
3697
|
+
"m main; n add worker; w/1-4 worker; c control; r release; h history; b mailbox; d detach; ! dismiss error; double Ctrl-g forwards native Ctrl-g"
|
|
3631
3698
|
]);
|
|
3632
3699
|
connection.command([
|
|
3633
3700
|
"set-hook",
|
|
@@ -3654,8 +3721,6 @@ var TmuxView = class {
|
|
|
3654
3721
|
view.serverIdentity
|
|
3655
3722
|
);
|
|
3656
3723
|
}
|
|
3657
|
-
view;
|
|
3658
|
-
stateFile;
|
|
3659
3724
|
connection;
|
|
3660
3725
|
shell(argv2) {
|
|
3661
3726
|
return `exec ${argv2.map(quote).join(" ")}`;
|
|
@@ -3827,7 +3892,7 @@ var TmuxView = class {
|
|
|
3827
3892
|
"set-option",
|
|
3828
3893
|
"-g",
|
|
3829
3894
|
"status-right",
|
|
3830
|
-
`${
|
|
3895
|
+
`${targetStatus(view)}${failure2} | Ctrl-g ?`
|
|
3831
3896
|
]);
|
|
3832
3897
|
}
|
|
3833
3898
|
input(pane, enabled) {
|
|
@@ -3850,6 +3915,32 @@ var TmuxView = class {
|
|
|
3850
3915
|
clients() {
|
|
3851
3916
|
return this.connection.command(["list-clients", "-F", "#{client_pid}"]);
|
|
3852
3917
|
}
|
|
3918
|
+
workerMenu(view) {
|
|
3919
|
+
const pane = view.workerPane ?? view.mainPane;
|
|
3920
|
+
this.connection.assertOwned(pane);
|
|
3921
|
+
const popup = [
|
|
3922
|
+
"tmux",
|
|
3923
|
+
"-S",
|
|
3924
|
+
this.connection.socket,
|
|
3925
|
+
"display-popup",
|
|
3926
|
+
"-E",
|
|
3927
|
+
"-t",
|
|
3928
|
+
pane,
|
|
3929
|
+
"-w",
|
|
3930
|
+
"90%",
|
|
3931
|
+
"-h",
|
|
3932
|
+
"80%",
|
|
3933
|
+
this.shell([
|
|
3934
|
+
view.node,
|
|
3935
|
+
view.worker,
|
|
3936
|
+
"worker-menu",
|
|
3937
|
+
"--view",
|
|
3938
|
+
this.stateFile,
|
|
3939
|
+
"--callback"
|
|
3940
|
+
])
|
|
3941
|
+
];
|
|
3942
|
+
this.connection.command(["run-shell", "-b", popup.map(quote).join(" ")]);
|
|
3943
|
+
}
|
|
3853
3944
|
copy(pane) {
|
|
3854
3945
|
this.connection.assertOwned(pane);
|
|
3855
3946
|
this.connection.command(["copy-mode", "-t", pane]);
|
|
@@ -3880,7 +3971,7 @@ async function waitFor(predicate, timeoutMs) {
|
|
|
3880
3971
|
const deadline = Date.now() + timeoutMs;
|
|
3881
3972
|
while (Date.now() < deadline) {
|
|
3882
3973
|
if (await predicate()) return;
|
|
3883
|
-
await new Promise((
|
|
3974
|
+
await new Promise((resolve5) => setTimeout(resolve5, 50));
|
|
3884
3975
|
}
|
|
3885
3976
|
throw new Error("writer_not_ready");
|
|
3886
3977
|
}
|
|
@@ -3916,6 +4007,7 @@ function dockLines(snapshot) {
|
|
|
3916
4007
|
),
|
|
3917
4008
|
"",
|
|
3918
4009
|
"Ctrl-g w: observe worker",
|
|
4010
|
+
"Ctrl-g n: add worker",
|
|
3919
4011
|
"Ctrl-g m: return to main",
|
|
3920
4012
|
"Ctrl-g c: acquire control",
|
|
3921
4013
|
"Ctrl-g r: release control",
|
|
@@ -3927,7 +4019,7 @@ function dockLines(snapshot) {
|
|
|
3927
4019
|
];
|
|
3928
4020
|
}
|
|
3929
4021
|
function coordinationLines(snapshot, view) {
|
|
3930
|
-
const target = `TARGET ${
|
|
4022
|
+
const target = `TARGET ${safe(targetStatus(view))}`;
|
|
3931
4023
|
if (view.failure)
|
|
3932
4024
|
return [
|
|
3933
4025
|
target,
|
|
@@ -4076,6 +4168,7 @@ function resolveEncoder(model = DEFAULT_MODEL_ID, options = {}) {
|
|
|
4076
4168
|
}
|
|
4077
4169
|
|
|
4078
4170
|
// src/config.ts
|
|
4171
|
+
var CONFIG_FILE = "pmcp.toml";
|
|
4079
4172
|
function embeddingSection(path, dir, value) {
|
|
4080
4173
|
if (value === void 0) return void 0;
|
|
4081
4174
|
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
@@ -4166,6 +4259,17 @@ var CATALOG_KEYS = /* @__PURE__ */ new Set([
|
|
|
4166
4259
|
"scopes",
|
|
4167
4260
|
"builtin"
|
|
4168
4261
|
]);
|
|
4262
|
+
function findConfig(start2) {
|
|
4263
|
+
let dir = resolve2(start2);
|
|
4264
|
+
for (; ; ) {
|
|
4265
|
+
const candidate = join23(dir, CONFIG_FILE);
|
|
4266
|
+
if (existsSync10(candidate)) return candidate;
|
|
4267
|
+
if (existsSync10(join23(dir, ".git"))) return null;
|
|
4268
|
+
const parent = dirname5(dir);
|
|
4269
|
+
if (parent === dir) return null;
|
|
4270
|
+
dir = parent;
|
|
4271
|
+
}
|
|
4272
|
+
}
|
|
4169
4273
|
function stringList(path, key, value) {
|
|
4170
4274
|
if (!Array.isArray(value) || !value.every((v) => typeof v === "string")) {
|
|
4171
4275
|
throw new ConfigError(path, `[catalog] ${key} must be a list of strings`);
|
|
@@ -4304,8 +4408,6 @@ var MailboxError = class extends Error {
|
|
|
4304
4408
|
this.retryable = retryable;
|
|
4305
4409
|
this.name = "MailboxError";
|
|
4306
4410
|
}
|
|
4307
|
-
code;
|
|
4308
|
-
retryable;
|
|
4309
4411
|
};
|
|
4310
4412
|
function failure(error) {
|
|
4311
4413
|
if (error instanceof MailboxError) {
|
|
@@ -5551,7 +5653,7 @@ ${content}
|
|
|
5551
5653
|
stopped = true;
|
|
5552
5654
|
});
|
|
5553
5655
|
while (!stopped) {
|
|
5554
|
-
await new Promise((
|
|
5656
|
+
await new Promise((resolve5) => setTimeout(resolve5, 1500));
|
|
5555
5657
|
await draw();
|
|
5556
5658
|
}
|
|
5557
5659
|
}
|
|
@@ -5669,6 +5771,11 @@ var handlers = {
|
|
|
5669
5771
|
close: closeView,
|
|
5670
5772
|
check: checkControl,
|
|
5671
5773
|
dismiss: ({ view, file }) => dismissFailure(file, view),
|
|
5774
|
+
"worker-menu": async (context) => {
|
|
5775
|
+
if (!context.renderer.clients()) throw new Error("view_client_unavailable");
|
|
5776
|
+
await releaseControl(context);
|
|
5777
|
+
context.renderer.workerMenu(context.view);
|
|
5778
|
+
},
|
|
5672
5779
|
management: async (context) => {
|
|
5673
5780
|
await releaseControl(context);
|
|
5674
5781
|
if (context.view.managementWindow) {
|
|
@@ -5802,6 +5909,70 @@ function runtime() {
|
|
|
5802
5909
|
return { node: realpathSync10(available.node), worker: bundled };
|
|
5803
5910
|
}
|
|
5804
5911
|
|
|
5912
|
+
// src/tui/startup/diagnostics.ts
|
|
5913
|
+
function remediation(tools, userManagerAvailable) {
|
|
5914
|
+
const steps = [];
|
|
5915
|
+
if (tools.platform !== "linux")
|
|
5916
|
+
steps.push(
|
|
5917
|
+
"Use a qualified Linux host for the terminal workspace. Other PMCP CLI features do not require this host profile."
|
|
5918
|
+
);
|
|
5919
|
+
const missing = ["tmux", "flock", "systemctl", "systemdRun"].filter(
|
|
5920
|
+
(name) => !tools[name]
|
|
5921
|
+
);
|
|
5922
|
+
if (missing.length)
|
|
5923
|
+
steps.push(
|
|
5924
|
+
`Install the missing tmux, util-linux or systemd tools for your distribution and add them to PATH: ${missing.join(", ")}.`
|
|
5925
|
+
);
|
|
5926
|
+
if (!tools.nodeSupported)
|
|
5927
|
+
steps.push(
|
|
5928
|
+
"Select Node.js 22 or later through your existing version manager and check node --version in this terminal."
|
|
5929
|
+
);
|
|
5930
|
+
if (tools.supported && !userManagerAvailable)
|
|
5931
|
+
steps.push(
|
|
5932
|
+
"Check systemctl --user status in your login session and restore access to its user manager before launching. Doctor does not start or repair services."
|
|
5933
|
+
);
|
|
5934
|
+
return steps;
|
|
5935
|
+
}
|
|
5936
|
+
function inspectTerminalHost() {
|
|
5937
|
+
const tools = dependencies();
|
|
5938
|
+
let userManagerAvailable = false;
|
|
5939
|
+
let reason;
|
|
5940
|
+
try {
|
|
5941
|
+
if (!tools.supported) throw new Error("tui_dependencies_unavailable");
|
|
5942
|
+
assertNativeHost();
|
|
5943
|
+
userManagerAvailable = true;
|
|
5944
|
+
} catch (error) {
|
|
5945
|
+
reason = error instanceof Error ? error.message : "native_host_unavailable";
|
|
5946
|
+
}
|
|
5947
|
+
const runtimes = Object.entries(runtimeExecutables).map(
|
|
5948
|
+
([id2, executable]) => {
|
|
5949
|
+
const path = locateExecutable(executable);
|
|
5950
|
+
return {
|
|
5951
|
+
id: id2,
|
|
5952
|
+
executable,
|
|
5953
|
+
installed: Boolean(path),
|
|
5954
|
+
...path ? { path } : {},
|
|
5955
|
+
nativeAcceptance: "not-assessed",
|
|
5956
|
+
authentication: "not-inspected"
|
|
5957
|
+
};
|
|
5958
|
+
}
|
|
5959
|
+
);
|
|
5960
|
+
return {
|
|
5961
|
+
schemaVersion: 1,
|
|
5962
|
+
ok: true,
|
|
5963
|
+
...tools,
|
|
5964
|
+
supported: tools.supported && userManagerAvailable,
|
|
5965
|
+
userManagerAvailable,
|
|
5966
|
+
...reason ? { reason } : {},
|
|
5967
|
+
runtimes,
|
|
5968
|
+
qualification: "Prerequisite availability only; native behavior and cgroup delegation require separate qualification.",
|
|
5969
|
+
remediation: remediation(tools, userManagerAvailable),
|
|
5970
|
+
nextCommands: ["pmcp tui plan", "pmcp tui recovery"],
|
|
5971
|
+
providerInvoked: false,
|
|
5972
|
+
authenticationInspected: false
|
|
5973
|
+
};
|
|
5974
|
+
}
|
|
5975
|
+
|
|
5805
5976
|
// src/tui/application/create.ts
|
|
5806
5977
|
import { join as join29 } from "path";
|
|
5807
5978
|
import { realpathSync as realpathSync11 } from "fs";
|
|
@@ -5880,8 +6051,9 @@ async function createView(input) {
|
|
|
5880
6051
|
|
|
5881
6052
|
// src/tui/application/invoke.ts
|
|
5882
6053
|
async function invokeTui(invocation) {
|
|
5883
|
-
if (
|
|
5884
|
-
|
|
6054
|
+
if (["launch", "plan", "recovery", "start-worker"].includes(invocation.action))
|
|
6055
|
+
throw new Error("startup_requires_cli_context");
|
|
6056
|
+
if (invocation.action === "doctor") return inspectTerminalHost();
|
|
5885
6057
|
if (invocation.action === "create") return createView(invocation.create);
|
|
5886
6058
|
if (invocation.action === "inspect")
|
|
5887
6059
|
return operate("inspect", invocation.viewFile);
|
|
@@ -5917,11 +6089,379 @@ function failedInvocation(action, error) {
|
|
|
5917
6089
|
};
|
|
5918
6090
|
}
|
|
5919
6091
|
|
|
6092
|
+
// src/tui/startup/workspace.ts
|
|
6093
|
+
import { existsSync as existsSync16 } from "fs";
|
|
6094
|
+
|
|
6095
|
+
// src/tui/startup/config.ts
|
|
6096
|
+
import { realpathSync as realpathSync12 } from "fs";
|
|
6097
|
+
import { resolve as resolve4 } from "path";
|
|
6098
|
+
var preference = [
|
|
6099
|
+
"codex-cli",
|
|
6100
|
+
"gemini-cli",
|
|
6101
|
+
"antigravity",
|
|
6102
|
+
"grok-cli",
|
|
6103
|
+
"claude-code"
|
|
6104
|
+
];
|
|
6105
|
+
function startupSettings(request2) {
|
|
6106
|
+
const path = request2.config ? resolve4(request2.cwd, request2.config) : findConfig(request2.cwd);
|
|
6107
|
+
const config = path ? readConfig(path) : void 0;
|
|
6108
|
+
const source2 = config?.raw.tui;
|
|
6109
|
+
const fail = (message) => {
|
|
6110
|
+
throw new ConfigError(path ?? "pmcp.toml", `[tui] ${message}`);
|
|
6111
|
+
};
|
|
6112
|
+
if (source2 !== void 0 && (source2 === null || typeof source2 !== "object" || Array.isArray(source2)))
|
|
6113
|
+
fail("must be a table");
|
|
6114
|
+
const values = source2 ?? {};
|
|
6115
|
+
const allowed2 = /* @__PURE__ */ new Set([
|
|
6116
|
+
"runtime",
|
|
6117
|
+
"memory_mb",
|
|
6118
|
+
"session_memory_mb",
|
|
6119
|
+
"max_active",
|
|
6120
|
+
"group_file"
|
|
6121
|
+
]);
|
|
6122
|
+
for (const key of Object.keys(values)) {
|
|
6123
|
+
if (!allowed2.has(key)) fail(`unknown key ${key}`);
|
|
6124
|
+
}
|
|
6125
|
+
const string = (key) => {
|
|
6126
|
+
const value = values[key];
|
|
6127
|
+
if (value === void 0) return void 0;
|
|
6128
|
+
if (typeof value !== "string" || !value.trim())
|
|
6129
|
+
fail(`${key} must be a non-empty string`);
|
|
6130
|
+
return value;
|
|
6131
|
+
};
|
|
6132
|
+
const integer2 = (key, fallback, min, max) => {
|
|
6133
|
+
const value = values[key] ?? fallback;
|
|
6134
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value))
|
|
6135
|
+
fail(`${key} must be an integer`);
|
|
6136
|
+
if (Number(value) < min || Number(value) > max)
|
|
6137
|
+
fail(`${key} must be between ${min} and ${max}`);
|
|
6138
|
+
return Number(value);
|
|
6139
|
+
};
|
|
6140
|
+
const configuredRuntime = string("runtime");
|
|
6141
|
+
if (configuredRuntime !== void 0 && configuredRuntime !== "auto" && !Object.hasOwn(runtimeExecutables, configuredRuntime))
|
|
6142
|
+
fail("runtime must be auto or a supported runtime ID");
|
|
6143
|
+
const available = preference.filter(
|
|
6144
|
+
(runtime3) => locateExecutable(runtimeExecutables[runtime3], request2.cwd)
|
|
6145
|
+
);
|
|
6146
|
+
let runtime2 = request2.runtime ?? configuredRuntime ?? "auto";
|
|
6147
|
+
if (runtime2 === "auto") runtime2 = available[0] ?? "codex-cli";
|
|
6148
|
+
if (!Object.hasOwn(runtimeExecutables, runtime2))
|
|
6149
|
+
fail("runtime must be a supported runtime ID");
|
|
6150
|
+
const memoryMb = integer2("memory_mb", 2048, 128, 4096);
|
|
6151
|
+
const sessionMemoryMb = integer2("session_memory_mb", 768, 32, 1536);
|
|
6152
|
+
if (sessionMemoryMb > memoryMb)
|
|
6153
|
+
fail("session memory exceeds the group budget");
|
|
6154
|
+
const groupFile = string("group_file");
|
|
6155
|
+
return {
|
|
6156
|
+
projectRoot: realpathSync12(config?.dir ?? request2.cwd),
|
|
6157
|
+
...config ? { configFile: config.path } : {},
|
|
6158
|
+
runtime: runtime2,
|
|
6159
|
+
allowedRuntimes: [.../* @__PURE__ */ new Set([...available, runtime2])],
|
|
6160
|
+
memoryMb,
|
|
6161
|
+
sessionMemoryMb,
|
|
6162
|
+
maxActive: integer2("max_active", 2, 1, 5),
|
|
6163
|
+
...groupFile ? { groupFile: resolve4(config?.dir ?? request2.cwd, groupFile) } : {}
|
|
6164
|
+
};
|
|
6165
|
+
}
|
|
6166
|
+
|
|
6167
|
+
// src/tui/startup/state.ts
|
|
6168
|
+
import { createHash as createHash8 } from "crypto";
|
|
6169
|
+
import { existsSync as existsSync15, lstatSync as lstatSync8, mkdirSync as mkdirSync6 } from "fs";
|
|
6170
|
+
import { homedir as homedir6 } from "os";
|
|
6171
|
+
import { dirname as dirname9, join as join30 } from "path";
|
|
6172
|
+
function startupFile(projectRoot) {
|
|
6173
|
+
const root = join30(
|
|
6174
|
+
process.env.XDG_STATE_HOME ?? join30(homedir6(), ".local/state"),
|
|
6175
|
+
"pmcp/launcher"
|
|
6176
|
+
);
|
|
6177
|
+
const key = createHash8("sha256").update(projectRoot).digest("hex");
|
|
6178
|
+
return join30(root, `${key}.json`);
|
|
6179
|
+
}
|
|
6180
|
+
function readStartup(file, projectRoot) {
|
|
6181
|
+
if (!existsSync15(file)) return null;
|
|
6182
|
+
const value = object(readPrivateJson(file));
|
|
6183
|
+
if (value.schemaVersion !== 1 || value.projectRoot !== projectRoot)
|
|
6184
|
+
throw new Error("foreign_startup_state");
|
|
6185
|
+
if (!["creating", "starting", "view-pending", "ready"].includes(
|
|
6186
|
+
String(value.phase)
|
|
6187
|
+
))
|
|
6188
|
+
throw new Error("invalid_startup_state");
|
|
6189
|
+
text(value.runtime, "runtime");
|
|
6190
|
+
if (value.phase !== "creating") {
|
|
6191
|
+
text(value.groupFile, "group_file");
|
|
6192
|
+
text(value.generation, "generation");
|
|
6193
|
+
}
|
|
6194
|
+
if (value.phase === "ready") text(value.viewFile, "view_file");
|
|
6195
|
+
return value;
|
|
6196
|
+
}
|
|
6197
|
+
function saveStartup(file, state) {
|
|
6198
|
+
privateJson(file, state);
|
|
6199
|
+
}
|
|
6200
|
+
|
|
6201
|
+
// src/tui/startup/workspace.ts
|
|
6202
|
+
function succeeded(receipt) {
|
|
6203
|
+
if (receipt.ok !== true)
|
|
6204
|
+
throw new Error(String(receipt.error ?? "startup_operation_failed"));
|
|
6205
|
+
return receipt;
|
|
6206
|
+
}
|
|
6207
|
+
async function prepareWorkspace(request2) {
|
|
6208
|
+
const settings = startupSettings(request2);
|
|
6209
|
+
const file = startupFile(settings.projectRoot);
|
|
6210
|
+
let state = readStartup(file, settings.projectRoot);
|
|
6211
|
+
if (state && !["ready", "view-pending"].includes(state.phase)) {
|
|
6212
|
+
if (!request2.fresh || state.phase === "creating")
|
|
6213
|
+
throw new Error("startup_requires_recovery");
|
|
6214
|
+
}
|
|
6215
|
+
if (state && settings.groupFile && state.groupFile !== settings.groupFile)
|
|
6216
|
+
throw new Error("startup_group_conflict");
|
|
6217
|
+
if (state) {
|
|
6218
|
+
const group = new GroupStore(state.groupFile).state.group;
|
|
6219
|
+
assertGroup(group, settings.projectRoot, state.generation);
|
|
6220
|
+
if (group.state === "paused") throw new Error("startup_group_paused");
|
|
6221
|
+
if (request2.fresh) {
|
|
6222
|
+
if (group.state !== "stopped")
|
|
6223
|
+
throw new Error("workspace_already_running");
|
|
6224
|
+
state = null;
|
|
6225
|
+
} else {
|
|
6226
|
+
if (group.state !== "ready") throw new Error("startup_requires_recovery");
|
|
6227
|
+
if (request2.runtime && request2.runtime !== state.runtime)
|
|
6228
|
+
throw new Error("startup_runtime_conflict");
|
|
6229
|
+
}
|
|
6230
|
+
}
|
|
6231
|
+
if (!state && settings.groupFile) {
|
|
6232
|
+
const store = new GroupStore(settings.groupFile);
|
|
6233
|
+
const group = store.state.group;
|
|
6234
|
+
assertGroup(group, settings.projectRoot);
|
|
6235
|
+
if (group.state !== "ready") throw new Error("startup_group_not_ready");
|
|
6236
|
+
state = {
|
|
6237
|
+
schemaVersion: 1,
|
|
6238
|
+
projectRoot: settings.projectRoot,
|
|
6239
|
+
phase: "view-pending",
|
|
6240
|
+
groupFile: store.path,
|
|
6241
|
+
generation: group.generation,
|
|
6242
|
+
runtime: settings.runtime
|
|
6243
|
+
};
|
|
6244
|
+
saveStartup(file, state);
|
|
6245
|
+
}
|
|
6246
|
+
if (!state) {
|
|
6247
|
+
state = {
|
|
6248
|
+
schemaVersion: 1,
|
|
6249
|
+
projectRoot: settings.projectRoot,
|
|
6250
|
+
phase: "creating",
|
|
6251
|
+
runtime: settings.runtime
|
|
6252
|
+
};
|
|
6253
|
+
saveStartup(file, state);
|
|
6254
|
+
const created = succeeded(
|
|
6255
|
+
await invoke({
|
|
6256
|
+
family: "group",
|
|
6257
|
+
action: "create",
|
|
6258
|
+
input: {
|
|
6259
|
+
projectRoot: settings.projectRoot,
|
|
6260
|
+
allowedRuntimes: [...settings.allowedRuntimes],
|
|
6261
|
+
memoryMb: settings.memoryMb,
|
|
6262
|
+
maxActive: settings.maxActive
|
|
6263
|
+
}
|
|
6264
|
+
})
|
|
6265
|
+
);
|
|
6266
|
+
state.groupFile = String(created.groupFile);
|
|
6267
|
+
state.generation = created.group.generation;
|
|
6268
|
+
state.phase = "starting";
|
|
6269
|
+
saveStartup(file, state);
|
|
6270
|
+
succeeded(
|
|
6271
|
+
await invoke({
|
|
6272
|
+
family: "session",
|
|
6273
|
+
action: "start",
|
|
6274
|
+
groupFile: state.groupFile,
|
|
6275
|
+
generation: state.generation,
|
|
6276
|
+
input: {
|
|
6277
|
+
runtime: settings.runtime,
|
|
6278
|
+
role: "main",
|
|
6279
|
+
memoryMb: settings.sessionMemoryMb
|
|
6280
|
+
}
|
|
6281
|
+
})
|
|
6282
|
+
);
|
|
6283
|
+
state.phase = "view-pending";
|
|
6284
|
+
saveStartup(file, state);
|
|
6285
|
+
}
|
|
6286
|
+
const observed = succeeded(
|
|
6287
|
+
await invoke({
|
|
6288
|
+
family: "session",
|
|
6289
|
+
action: "list",
|
|
6290
|
+
groupFile: state.groupFile
|
|
6291
|
+
})
|
|
6292
|
+
);
|
|
6293
|
+
const sessions = observed.sessions;
|
|
6294
|
+
const main = sessions.find(
|
|
6295
|
+
(session) => session.role === "main" && session.alive
|
|
6296
|
+
);
|
|
6297
|
+
if (!main) throw new Error("startup_main_unavailable");
|
|
6298
|
+
state.runtime = main.runtime;
|
|
6299
|
+
const existing = state.viewFile && existsSync16(state.viewFile) ? readView(state.viewFile).view : void 0;
|
|
6300
|
+
if (existing) {
|
|
6301
|
+
if (existing.groupFile !== state.groupFile || existing.generation !== state.generation)
|
|
6302
|
+
throw new Error("foreign_startup_view");
|
|
6303
|
+
}
|
|
6304
|
+
if (!existing || existing.state === "closed") {
|
|
6305
|
+
const created = await createView({
|
|
6306
|
+
groupFile: state.groupFile,
|
|
6307
|
+
columns: process.stdout.columns ?? Number(process.env.PMCP_TUI_COLUMNS),
|
|
6308
|
+
rows: process.stdout.rows ?? Number(process.env.PMCP_TUI_ROWS)
|
|
6309
|
+
});
|
|
6310
|
+
state.viewFile = created.viewFile;
|
|
6311
|
+
}
|
|
6312
|
+
state.phase = "ready";
|
|
6313
|
+
saveStartup(file, state);
|
|
6314
|
+
return {
|
|
6315
|
+
schemaVersion: 1,
|
|
6316
|
+
ok: true,
|
|
6317
|
+
viewFile: state.viewFile,
|
|
6318
|
+
groupFile: state.groupFile,
|
|
6319
|
+
generation: state.generation,
|
|
6320
|
+
runtime: state.runtime,
|
|
6321
|
+
readOnly: true
|
|
6322
|
+
};
|
|
6323
|
+
}
|
|
6324
|
+
function assertGroup(group, projectRoot, generation) {
|
|
6325
|
+
if (group.projectRoot !== projectRoot)
|
|
6326
|
+
throw new Error("startup_project_mismatch");
|
|
6327
|
+
if (generation && group.generation !== generation)
|
|
6328
|
+
throw new Error("stale_group_generation");
|
|
6329
|
+
}
|
|
6330
|
+
|
|
6331
|
+
// src/tui/application/worker-start.ts
|
|
6332
|
+
async function startViewWorker(file, runtime2, memoryMb) {
|
|
6333
|
+
const { view, common } = openView(file);
|
|
6334
|
+
if (view.state !== "open") throw new Error("view_closed");
|
|
6335
|
+
const observed = await common.snapshot();
|
|
6336
|
+
if (observed.group.state !== "ready") throw new Error("group_not_ready");
|
|
6337
|
+
const main = observed.sessions.find(
|
|
6338
|
+
(session) => session.id === view.mainId && session.role === "main"
|
|
6339
|
+
);
|
|
6340
|
+
if (!main?.alive) throw new Error("startup_main_unavailable");
|
|
6341
|
+
return common.call("session", "start", {
|
|
6342
|
+
input: { role: "worker", runtime: runtime2, memoryMb: memoryMb ?? main.memoryMb }
|
|
6343
|
+
});
|
|
6344
|
+
}
|
|
6345
|
+
|
|
6346
|
+
// src/cli/entry.ts
|
|
6347
|
+
function interactiveEntry(terminal) {
|
|
6348
|
+
if (!terminal.stdin || !terminal.stdout) return false;
|
|
6349
|
+
if (!terminal.term || terminal.term === "dumb") return false;
|
|
6350
|
+
return !terminal.ci || terminal.ci === "false" || terminal.ci === "0";
|
|
6351
|
+
}
|
|
6352
|
+
|
|
6353
|
+
// src/tui/startup/host.ts
|
|
6354
|
+
function terminalSupport() {
|
|
6355
|
+
try {
|
|
6356
|
+
if (!dependencies().supported)
|
|
6357
|
+
throw new Error("tui_dependencies_unavailable");
|
|
6358
|
+
assertNativeHost();
|
|
6359
|
+
planLayout(process.stdout.columns ?? 0, process.stdout.rows ?? 0);
|
|
6360
|
+
return { supported: true };
|
|
6361
|
+
} catch (error) {
|
|
6362
|
+
return {
|
|
6363
|
+
supported: false,
|
|
6364
|
+
reason: error instanceof Error ? error.message : "terminal_unavailable"
|
|
6365
|
+
};
|
|
6366
|
+
}
|
|
6367
|
+
}
|
|
6368
|
+
function requireTerminal() {
|
|
6369
|
+
if (!interactiveEntry({
|
|
6370
|
+
stdin: process.stdin.isTTY === true,
|
|
6371
|
+
stdout: process.stdout.isTTY === true,
|
|
6372
|
+
term: process.env.TERM,
|
|
6373
|
+
ci: process.env.CI
|
|
6374
|
+
}))
|
|
6375
|
+
throw new Error("terminal_required");
|
|
6376
|
+
const support = terminalSupport();
|
|
6377
|
+
if (!support.supported) throw new Error(support.reason);
|
|
6378
|
+
}
|
|
6379
|
+
|
|
6380
|
+
// src/tui/presentation/runtime-choice.ts
|
|
6381
|
+
import { createInterface } from "readline/promises";
|
|
6382
|
+
async function chooseRuntime(prompt, choices, preferred) {
|
|
6383
|
+
if (!choices.length) throw new Error("runtime_executable_unavailable");
|
|
6384
|
+
const fallback = Math.max(choices.indexOf(preferred), 0);
|
|
6385
|
+
process.stderr.write(
|
|
6386
|
+
`${choices.map((runtime2, index) => `${index + 1}. ${runtime2}`).join("\n")}
|
|
6387
|
+
`
|
|
6388
|
+
);
|
|
6389
|
+
while (true) {
|
|
6390
|
+
const answer = (await prompt.question(
|
|
6391
|
+
`Runtime [1-${choices.length}, Enter=${fallback + 1}, q=cancel] `
|
|
6392
|
+
)).trim();
|
|
6393
|
+
if (answer === "q") return void 0;
|
|
6394
|
+
if (answer === "") return choices[fallback];
|
|
6395
|
+
if (!/^\d+$/.test(answer)) continue;
|
|
6396
|
+
const index = Number(answer) - 1;
|
|
6397
|
+
if (Number.isSafeInteger(index) && index >= 0 && index < choices.length)
|
|
6398
|
+
return choices[index];
|
|
6399
|
+
}
|
|
6400
|
+
}
|
|
6401
|
+
async function confirmRuntimeStart(prompt, runtime2) {
|
|
6402
|
+
const answer = await prompt.question(
|
|
6403
|
+
`Start ${runtime2}? [Enter=start, q=cancel] `
|
|
6404
|
+
);
|
|
6405
|
+
return answer.trim() === "";
|
|
6406
|
+
}
|
|
6407
|
+
function terminalPrompt() {
|
|
6408
|
+
return createInterface({ input: process.stdin, output: process.stderr });
|
|
6409
|
+
}
|
|
6410
|
+
|
|
6411
|
+
// src/tui/presentation/worker-menu.ts
|
|
6412
|
+
async function runWorkerMenu(file) {
|
|
6413
|
+
requireTerminal();
|
|
6414
|
+
const { view, common } = openView(file);
|
|
6415
|
+
if (view.state !== "open") throw new Error("view_closed");
|
|
6416
|
+
const observed = await common.snapshot();
|
|
6417
|
+
const group = observed.group;
|
|
6418
|
+
if (group.state !== "ready") throw new Error("startup_group_not_ready");
|
|
6419
|
+
const choices = group.allowedRuntimes.filter((runtime2) => {
|
|
6420
|
+
const executable = runtimeExecutables[runtime2];
|
|
6421
|
+
if (!executable) return false;
|
|
6422
|
+
if (group.profile === "docker")
|
|
6423
|
+
return Boolean(group.dockerPolicy?.commands[runtime2]);
|
|
6424
|
+
return Boolean(locateExecutable(executable, group.projectRoot));
|
|
6425
|
+
});
|
|
6426
|
+
const main = observed.sessions.find((session) => session.id === view.mainId);
|
|
6427
|
+
if (!main?.alive) throw new Error("startup_main_unavailable");
|
|
6428
|
+
process.stderr.write(`PMCP WORKER | ${group.projectRoot}
|
|
6429
|
+
`);
|
|
6430
|
+
const prompt = terminalPrompt();
|
|
6431
|
+
try {
|
|
6432
|
+
const runtime2 = await chooseRuntime(prompt, choices, main.runtime);
|
|
6433
|
+
if (!runtime2 || !await confirmRuntimeStart(prompt, runtime2)) return;
|
|
6434
|
+
await startViewWorker(file, runtime2);
|
|
6435
|
+
} finally {
|
|
6436
|
+
prompt.close();
|
|
6437
|
+
}
|
|
6438
|
+
}
|
|
6439
|
+
|
|
5920
6440
|
// src/tui/application/worker.ts
|
|
5921
6441
|
var [role, ...argv] = process.argv.slice(2);
|
|
5922
6442
|
var callback2 = argv.includes("--callback");
|
|
5923
6443
|
try {
|
|
5924
|
-
if (role === "
|
|
6444
|
+
if (role === "startup") {
|
|
6445
|
+
const [encoded, ...extra] = argv;
|
|
6446
|
+
if (!encoded || extra.length || encoded.length > 16384 || !/^[A-Za-z0-9_-]+$/.test(encoded))
|
|
6447
|
+
throw new Error("invalid_startup_arguments");
|
|
6448
|
+
const request2 = JSON.parse(
|
|
6449
|
+
Buffer.from(encoded, "base64url").toString("utf8")
|
|
6450
|
+
);
|
|
6451
|
+
if (typeof request2.cwd !== "string" || !request2.cwd)
|
|
6452
|
+
throw new Error("invalid_startup_arguments");
|
|
6453
|
+
const result = await prepareWorkspace(request2);
|
|
6454
|
+
process.stdout.write(`${JSON.stringify(result)}
|
|
6455
|
+
`);
|
|
6456
|
+
} else if (role === "worker-menu") {
|
|
6457
|
+
const invocation = parseTui(
|
|
6458
|
+
parseArgs(
|
|
6459
|
+
["worker-menu", ...argv.filter((arg) => arg !== "--callback")],
|
|
6460
|
+
tuiOptions
|
|
6461
|
+
)
|
|
6462
|
+
);
|
|
6463
|
+
await runWorkerMenu(invocation.viewFile);
|
|
6464
|
+
} else if (role === "attach") {
|
|
5925
6465
|
const [file, sessionId, leaseFile] = argv;
|
|
5926
6466
|
if (!file || !sessionId) throw new Error("invalid_attachment_arguments");
|
|
5927
6467
|
await waitFor(() => {
|
|
@@ -5953,7 +6493,14 @@ try {
|
|
|
5953
6493
|
tuiOptions
|
|
5954
6494
|
)
|
|
5955
6495
|
);
|
|
5956
|
-
if (
|
|
6496
|
+
if ([
|
|
6497
|
+
"create",
|
|
6498
|
+
"doctor",
|
|
6499
|
+
"launch",
|
|
6500
|
+
"plan",
|
|
6501
|
+
"recovery",
|
|
6502
|
+
"start-worker"
|
|
6503
|
+
].includes(invocation.action))
|
|
5957
6504
|
throw new Error("unsupported_worker_operation");
|
|
5958
6505
|
const result = role === "locked" ? await operate(
|
|
5959
6506
|
invocation.action,
|
|
@@ -5972,7 +6519,8 @@ try {
|
|
|
5972
6519
|
const file = argv[index + 1];
|
|
5973
6520
|
if (index >= 0 && file) {
|
|
5974
6521
|
try {
|
|
5975
|
-
|
|
6522
|
+
const action = role === "worker-menu" ? role : argv[0] ?? "callback";
|
|
6523
|
+
recordCallbackFailure(file, action, error);
|
|
5976
6524
|
const { view } = readView(file);
|
|
5977
6525
|
if (view.state === "open") new TmuxView(view, file).status(view);
|
|
5978
6526
|
} catch {
|