seatmesh 1.0.0 → 1.0.3

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 (56) hide show
  1. package/dist/commands/ack-cli.d.ts +4 -0
  2. package/dist/commands/ack-cli.d.ts.map +1 -0
  3. package/dist/commands/ack-cli.js +105 -0
  4. package/dist/commands/ack-cli.js.map +1 -0
  5. package/dist/commands/checkback-cli.d.ts.map +1 -1
  6. package/dist/commands/checkback-cli.js +1 -0
  7. package/dist/commands/checkback-cli.js.map +1 -1
  8. package/dist/commands/notify-cli.d.ts.map +1 -1
  9. package/dist/commands/notify-cli.js +69 -10
  10. package/dist/commands/notify-cli.js.map +1 -1
  11. package/dist/commands/target-cli.d.ts +4 -0
  12. package/dist/commands/target-cli.d.ts.map +1 -0
  13. package/dist/commands/target-cli.js +146 -0
  14. package/dist/commands/target-cli.js.map +1 -0
  15. package/dist/main.js +266 -77
  16. package/dist/main.js.map +1 -1
  17. package/dist/setup/agent-context-init.d.ts.map +1 -1
  18. package/dist/setup/agent-context-init.js +11 -5
  19. package/dist/setup/agent-context-init.js.map +1 -1
  20. package/dist/setup/init.d.ts +23 -3
  21. package/dist/setup/init.d.ts.map +1 -1
  22. package/dist/setup/init.js +71 -3
  23. package/dist/setup/init.js.map +1 -1
  24. package/dist/setup/update.d.ts +4 -0
  25. package/dist/setup/update.d.ts.map +1 -1
  26. package/dist/setup/update.js +63 -26
  27. package/dist/setup/update.js.map +1 -1
  28. package/dist/setup/update.test.d.ts +2 -0
  29. package/dist/setup/update.test.d.ts.map +1 -0
  30. package/dist/setup/update.test.js +50 -0
  31. package/dist/setup/update.test.js.map +1 -0
  32. package/dist/ui/banner.d.ts +17 -2
  33. package/dist/ui/banner.d.ts.map +1 -1
  34. package/dist/ui/banner.js +76 -2
  35. package/dist/ui/banner.js.map +1 -1
  36. package/dist/ui/banner.test.d.ts +2 -0
  37. package/dist/ui/banner.test.d.ts.map +1 -0
  38. package/dist/ui/banner.test.js +47 -0
  39. package/dist/ui/banner.test.js.map +1 -0
  40. package/dist/ui/version-nudge.d.ts +16 -2
  41. package/dist/ui/version-nudge.d.ts.map +1 -1
  42. package/dist/ui/version-nudge.js +80 -3
  43. package/dist/ui/version-nudge.js.map +1 -1
  44. package/dist/ui/version-nudge.test.js +31 -3
  45. package/dist/ui/version-nudge.test.js.map +1 -1
  46. package/package.json +6 -6
  47. package/profiles/minimal/mesh.config.yaml +1 -1
  48. package/profiles/minimal/paths.json +2 -2
  49. package/profiles/minimal/roles/common.yaml +3 -0
  50. package/templates/init/AGENTS.md +87 -0
  51. package/templates/init/mesh.config.yaml +10 -6
  52. package/templates/init/roles/common.yaml +4 -1
  53. package/templates/init/roles/manager.yaml +3 -3
  54. package/templates/init/roles/mini.yaml +4 -4
  55. package/templates/init/roles/secretary.yaml +2 -2
  56. package/templates/init/roles/worker.yaml +5 -5
package/dist/main.js CHANGED
@@ -1,13 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from "node:child_process";
3
3
  import { findDotSmConfig } from "@seat-mesh/core";
4
- import { loadProfile, profilePaths, loadRoleIndex, renderRoleIndex, validateRoleIndex, runStackPassthrough, roleAllows, isCoordKind, isManagerKind, managerColumnIds, addBaseColumn, removeBaseColumn, listBaseColumns, } from "@seat-mesh/core";
4
+ import { loadProfile, profilePaths, loadRoleIndex, renderRoleIndex, validateRoleIndex, runStackPassthrough, roleAllows, isCoordKind, isManagerKind, managerColumnIds, addBaseColumn, removeBaseColumn, listBaseColumns, chatRoomConfigForLoaded, clearAckEndedSync, } from "@seat-mesh/core";
5
5
  import { snapshotConnectivity, formatStatus } from "@seat-mesh/connectivity";
6
6
  import { createRegistryForProfile } from "@seat-mesh/providers";
7
- import { printWhoami, whoamiJson, whoamiJsonWithValidate, validateWhoamiRoleIndex, printAgentCard, printAgentContext, printAgentContextAllRoles, runWhoami, roleKindFromWhoami, requireCoordRole, requireInboxLifecycleRole, capturePaneSnapshot, listSessionPanes, sessionAttach, sessionUp, sessionStatus, relayoutMeshSession, realignAllLayouts, reloadMesh, ensureMeshInbox, startMeshInbox, stopMeshInbox, restartMeshInbox, printMeshInboxStatus, waitForMeshInbox, printInboxMeta, tailInboxLog, listInboxInstances, parseInboxStatusFlags, inboxStatusArgv, listInbox, resolveInbox, secretaryLaunch, secretaryRestart, secretaryDispatch, secretaryCollect, secretaryMeshWatch, secretarySupervise, secretaryStatus, printMiniList, miniSpawn, miniPrompt, miniDone, miniSpawnAll, runMeshSmoke, printSmokeResults, launchSession, printLaunchResults, enqueuePrompt, injectPromptDirect, printPeerVerify, runPeerVerify, runRemind, printRemindResults, runNight, runContinue, printContinueResults, runSlotAdvice, verifyMeshSession, printVerify, labelMeshSession, liveMeshSession, ensureBaseLayout, realignBaseLayout, applyMeshSessionBorders, runFlush, printFlushResults, runSwitch, runSet, runTag, setPaneTitle, setPaneStatus, printSeatContexts, runPeek, runPaneMeta, runPpa, runToSlot, runToMini, runPeer, applyMeshState, saveMeshSession, saveMeshSessionDetailed, assertSaveAllowed, formatSaveSummary, submitPaneOp, clearPaneOpsQueue, printPaneOpsList, printRelayoutPlan, assertRelayoutSafe, buildFullColdStartBrief, enqueueColdStart, runSeatInit, applyAgentContractBundle, balanceLeadCommand, } from "@seat-mesh/tmux";
7
+ import { printWhoami, whoamiJson, whoamiJsonWithValidate, validateWhoamiRoleIndex, printAgentCard, printAgentContext, printAgentContextAllRoles, runWhoami, roleKindFromWhoami, requireCoordRole, requireInboxLifecycleRole, assertAgentDispatch, printAgentUnauthorized, stripAgentFromArgv, AGENT_META, capturePaneSnapshot, listSessionPanes, sessionAttach, sessionUp, sessionStatus, relayoutMeshSession, realignAllLayouts, reloadMesh, ensureMeshInbox, startMeshInbox, stopMeshInbox, restartMeshInbox, meshInboxEverConfigured, meshInboxPort, printMeshInboxStatus, waitForMeshInbox, printInboxMeta, tailInboxLog, listInboxInstances, parseInboxStatusFlags, inboxStatusArgv, listInbox, resolveInbox, secretaryLaunch, secretaryRestart, secretaryDispatch, secretaryCollect, secretaryMeshWatch, secretarySupervise, secretaryStatus, printMiniList, miniSpawn, miniPrompt, miniDone, miniSpawnAll, runMeshSmoke, printSmokeResults, launchSession, printLaunchResults, enqueuePrompt, injectPromptDirect, printPeerVerify, runPeerVerify, runRemind, printRemindResults, runNight, runContinue, printContinueResults, runSlotAdvice, verifyMeshSession, printVerify, labelMeshSession, liveMeshSession, ensureBaseLayout, realignBaseLayout, applyMeshSessionBorders, runFlush, printFlushResults, runSwitch, runSet, runTag, setPaneTitle, setPaneStatus, printSeatContexts, runPeek, runPaneKind, runPaneMeta, runPpa, runToSlot, runToMini, runPeer, parseRemotePeerTarget, runRemotePeer, applyMeshState, saveMeshSession, saveMeshSessionDetailed, assertSaveAllowed, formatSaveSummary, submitPaneOp, clearPaneOpsQueue, printPaneOpsList, printRelayoutPlan, assertRelayoutSafe, buildFullColdStartBrief, enqueueColdStart, runSeatInit, applyAgentContractBundle, balanceLeadCommand, } from "@seat-mesh/tmux";
8
8
  import { parseAgentApplyArgs } from "@seat-mesh/core";
9
9
  import { buildChatCommands } from "./commands/chat-cli.js";
10
10
  import { buildCheckbackCommands } from "./commands/checkback-cli.js";
11
+ import { buildTargetCommands } from "./commands/target-cli.js";
12
+ import { buildAckCommands } from "./commands/ack-cli.js";
11
13
  import { buildNotifyCommand } from "./commands/notify-cli.js";
12
14
  import { buildPreviewCommand } from "./commands/preview-cli.js";
13
15
  import { buildContractLockCommands } from "./commands/contract-lock-cli.js";
@@ -46,8 +48,11 @@ function usage(loaded) {
46
48
  Setup (run once per project, by a human)
47
49
  start init (if no .sm/ here) + session up, in one step
48
50
  init [--force] [--seats-root PATH] [--name NAME] create .sm/ dotdir
51
+ session init <sm-name> [--force] [--name NAME] create .sm-<name>/ (multi-config)
49
52
  sessions [pick|list|attach|forget|register] [--json] global registry + TUI picker
50
- update [--dry-run] [--migrate] refresh _vendor templates + paths.json (not npm upgrade)
53
+ update [--dry-run] [--migrate] [--no-restart-inbox]
54
+ refresh _vendor (file-by-file) + paths.json; restart inbox
55
+ version [--json] [--check-registry] CLI vs npm latest vs profile .seatmesh-version
51
56
 
52
57
  Status / diagnostics
53
58
  report [--json] [--verbose] stack status (one line default; --verbose = full section dump)
@@ -55,7 +60,7 @@ Status / diagnostics
55
60
  test smoke: layout, providers, inbox, proxy
56
61
 
57
62
  Session / layout (infra shape, operator or manager)
58
- session attach|up|status
63
+ session attach|up|status|init <sm-name>
59
64
  reload [--layout] rebuild engine + labels (no session kill; --layout re-grids)
60
65
  layout [--no-leads] [--dry-run] [--yes] workers + minis grid (queued)
61
66
  layout column list|add <id> [--cli P] [--after ID] [--co-typed]|remove <id>
@@ -66,78 +71,122 @@ Session / layout (infra shape, operator or manager)
66
71
  labels re-apply @mesh_* + border strip
67
72
  inbox [--json] [--wait N] [--meta] | inbox list|resolve|log|instances|stop|restart
68
73
 
69
- Agent runtime (what a pane calls on itself, every turn)
70
- whoami [target] [--validate] | cold-start [--inject] | seat init|now|assign|mark|task|remind
71
- agent [target] scoped can/cannot for this pane (profile role)
72
- agent context [roles|init] registered read_first/files; init scaffolds .sm + validates
73
- func <id> <args...> attached external (funcs: in mesh.config.yaml)
74
- contexts [--json] | peek | pane-meta | ppa
75
- switch | handoff | set | tag <target|self> <id|--auto> | title | status
76
- night on|off|status | continue <slot|all> (manager + night on)
77
- slot-advice <slot> [--send] [note...] (manager; worktree/DB/Redis heuristics)
78
- prompt | remind | flush
74
+ Agent runtime (pane — always via gateway)
75
+ agent scoped can/cannot for this pane
76
+ agent <cmd> … run <cmd> if allowed; else UNAUTHORIZED
77
+ agent context [roles|init] registered read_first/files; init scaffolds .sm
78
+ agent apply|preflight … contract apply
79
79
 
80
- Coordination / comms (pane to pane)
81
- assign <target> <text...> FOCUS NOW + TASK + peer SENT (do not hand-edit FOCUS)
82
- peer <target> <msg...> FYI/ACK only — work goes through assign
83
- to-slot | to-mini <msg...> enqueue (daemon injects) — to-master retired, use room say
84
- secretary start|dispatch|collect|status|watch …
85
- mini list|spawn|prompt|done|dispatch-all
86
- checkback start|list|cancel|cancel-all|reset|ack (alias: patience)
87
- coord expect <target> <hub> <snippet...> arm coord-expect on manager (verify + re-assign)
88
- room | chat | index | proxy | providers | manager | stack | profile show
89
- notify <session> <check> [--url URL] desktop toast (seat from TMUX pane)
90
- notify yesno <title> <body> [--target secretary] Yes/No links -> peer (notify-act)
91
- preview <file...> [--set days] [--notify] publish markdown to mdview.io
92
- launch [--now] [targets…]
80
+ Shared/operator (outside agent — humans + session ops)
81
+ session | update | init | report | test | layout | save | inbox restart | target …
93
82
 
94
- --profile <dir|yaml> override config (default: .sm/ walk-up or bundled profile)
83
+ Examples: agent whoami | agent kind slot-1 | agent peer <t> "<msg>" | agent ack <id> "<note>" | agent cb list
84
+ Operator EOD: target add "finish s13 tickets" [--deadline eod|6h] · target list · target done <id> · target triage <id>
85
+
86
+ --profile <dir|yaml> optional; default walk-up .sm/; multi-config: --profile .sm-<name>
95
87
  `);
96
88
  }
89
+ function printPlainUsage(profileArg) {
90
+ if (profileArg) {
91
+ try {
92
+ usage(meshLoaded(profileArg));
93
+ return;
94
+ }
95
+ catch {
96
+ /* fall through — plain help without profile load */
97
+ }
98
+ }
99
+ usage();
100
+ }
97
101
  async function main() {
98
- const { maybePrintVersionNudge } = await import("./ui/version-nudge.js");
99
- maybePrintVersionNudge();
100
102
  const { profile: profileArg, rest } = parseArgs(process.argv.slice(2));
101
103
  const [cmd, sub, ...tail] = rest;
102
- if (!cmd || (cmd.startsWith("-") && cmd !== "-h" && cmd !== "--help")) {
103
- const json = rest.includes("--json");
104
- if (!profileArg && !findDotSmConfig()) {
105
- const { printDiscoveryReport } = await import("./report/discovery-report.js");
106
- const report = await printDiscoveryReport({ json });
107
- process.exit(report.ok ? 0 : 1);
104
+ // Bare `npx seatmesh` / help: logo+version once per terminal; later invocations plain usage.
105
+ const helpCmd = cmd === "-h" || cmd === "--help" || cmd === "help";
106
+ const blankCmd = !cmd || (cmd.startsWith("-") && !helpCmd);
107
+ if (blankCmd || helpCmd) {
108
+ const { printSeatmeshBanner } = await import("./ui/banner.js");
109
+ const { formatHelpVersionBlock, installKindLabel, readInstalledCliVersion, resolveLatestRegistryVersion, } = await import("./ui/version-nudge.js");
110
+ const showedBrand = printSeatmeshBanner({ tagline: true });
111
+ if (showedBrand) {
112
+ const installed = readInstalledCliVersion();
113
+ const latest = resolveLatestRegistryVersion(false);
114
+ for (const line of formatHelpVersionBlock(installed, latest, installKindLabel())) {
115
+ console.log(line);
116
+ }
117
+ console.log("");
108
118
  }
109
- const loaded = meshLoaded(profileArg);
110
- const { printStatusReport } = await import("./report/status-report.js");
111
- const verbose = rest.includes("--verbose") || rest.includes("-v");
112
- const report = await printStatusReport(loaded, { json, verbose });
113
- process.exit(report.ok ? 0 : 1);
119
+ printPlainUsage(profileArg);
120
+ return;
114
121
  }
115
- if (cmd === "-h" || cmd === "--help" || cmd === "help") {
122
+ const { maybePrintVersionNudge } = await import("./ui/version-nudge.js");
123
+ maybePrintVersionNudge();
124
+ if (cmd === "version") {
125
+ const { printVersionInfo } = await import("./ui/version-nudge.js");
126
+ let profileDir;
116
127
  try {
117
- usage(profileArg ? meshLoaded(profileArg) : undefined);
128
+ profileDir = meshLoaded(profileArg).profileDir;
118
129
  }
119
130
  catch {
120
- usage();
131
+ /* no profile — CLI only */
121
132
  }
133
+ printVersionInfo({
134
+ profileDir,
135
+ json: rest.includes("--json"),
136
+ forceRegistryCheck: rest.includes("--check-registry"),
137
+ });
122
138
  return;
123
139
  }
124
140
  if (cmd === "update") {
125
141
  const { runUpdate } = await import("./setup/update.js");
142
+ const { formatInstallStatusLine, isNpxEphemeralInstall, readInstalledCliVersion, resolveLatestRegistryVersion, semverLess, } = await import("./ui/version-nudge.js");
126
143
  const dryRun = rest.includes("--dry-run");
127
144
  const migrate = rest.includes("--migrate");
145
+ const noRestartInbox = rest.includes("--no-restart-inbox");
146
+ const installed = readInstalledCliVersion();
147
+ const npmLatest = resolveLatestRegistryVersion(true);
148
+ console.log(formatInstallStatusLine(installed, npmLatest));
128
149
  const r = runUpdate({ profileArg, dryRun, migrate });
129
150
  console.log(`OK: update dryRun=${dryRun} migrate=${migrate}`);
151
+ console.log(` CLI running: ${r.packageVersion}`);
152
+ if (r.previousVersion != null) {
153
+ console.log(` profile .seatmesh-version: ${r.previousVersion} (last profile update — not npm "installed")`);
154
+ }
155
+ else {
156
+ console.log(` profile .seatmesh-version: (none yet)`);
157
+ }
158
+ if (npmLatest)
159
+ console.log(` npm latest: ${npmLatest}`);
160
+ if (npmLatest && semverLess(installed, npmLatest)) {
161
+ console.error(` WARN: CLI ${installed} < npm ${npmLatest} — upgrade: npm install -g seatmesh@latest or npx seatmesh@latest update`);
162
+ }
130
163
  console.log(` paths: ${r.pathsManifest}`);
131
164
  for (const line of r.refreshed)
132
165
  console.log(` refreshed: ${line}`);
133
166
  for (const line of r.skipped)
134
- console.log(` skipped (exists): ${line}`);
167
+ console.log(` skipped (unchanged): ${line}`);
135
168
  if (r.migrate) {
136
169
  for (const line of r.migrate.copied)
137
170
  console.log(` migrate copied: ${line}`);
138
171
  for (const line of r.migrate.skipped)
139
172
  console.log(` migrate skipped: ${line}`);
140
173
  }
174
+ if (isNpxEphemeralInstall()) {
175
+ console.log(" note: npx cache CLI — profile vendor updated only; npm package is re-fetched each npx run");
176
+ }
177
+ if (!dryRun && !noRestartInbox) {
178
+ const loaded = meshLoaded(profileArg);
179
+ if (meshInboxEverConfigured(loaded)) {
180
+ restartMeshInbox(loaded);
181
+ console.log(`OK: inbox restarted on :${meshInboxPort(loaded)} session=${loaded.sessionName}`);
182
+ }
183
+ else {
184
+ console.log(" inbox: skip restart (no inbox for this profile yet)");
185
+ }
186
+ }
187
+ else if (noRestartInbox) {
188
+ console.log(" inbox: skip restart (--no-restart-inbox)");
189
+ }
141
190
  return;
142
191
  }
143
192
  if (cmd === "migrate-runtime") {
@@ -194,7 +243,7 @@ async function main() {
194
243
  catch {
195
244
  /* non-fatal */
196
245
  }
197
- console.log(" next: npx seatmesh session up (or ./sm.sh if wired)");
246
+ console.log(" next: seatmesh session up");
198
247
  return;
199
248
  }
200
249
  if (cmd === "sessions") {
@@ -217,6 +266,37 @@ async function main() {
217
266
  return;
218
267
  }
219
268
  if (cmd === "session") {
269
+ if (sub === "init") {
270
+ const { normalizeSmProfileDir } = await import("@seat-mesh/core");
271
+ const rawName = tail[0];
272
+ if (!rawName || rawName.startsWith("-")) {
273
+ console.error("usage: session init <sm-name> [--force] [--name NAME]");
274
+ console.error(" creates .sm-<name>/ (default project config is still .sm via: init)");
275
+ console.error(" then: seatmesh --profile .sm-<name> session up");
276
+ process.exit(2);
277
+ }
278
+ const force = tail.includes("--force");
279
+ const nameIdx = tail.indexOf("--name");
280
+ const profileName = nameIdx >= 0 && tail[nameIdx + 1] ? tail[nameIdx + 1] : undefined;
281
+ const smDirName = normalizeSmProfileDir(rawName);
282
+ const r = runInit({ force, name: profileName, smDirName });
283
+ console.log(`OK: session init ${r.smDir}`);
284
+ console.log(` config: ${r.configPath}`);
285
+ console.log(` created: ${r.created.length} file(s)`);
286
+ if (r.skipped.length)
287
+ console.log(` skipped (exists): ${r.skipped.length}`);
288
+ try {
289
+ const { upsertGlobalSession } = await import("@seat-mesh/core");
290
+ await upsertGlobalSession(meshLoaded(r.configPath));
291
+ }
292
+ catch {
293
+ /* non-fatal */
294
+ }
295
+ console.log(` next: seatmesh --profile ${smDirName} session up`);
296
+ console.log(` agents: seatmesh --profile ${smDirName} agent …`);
297
+ console.log(` default .sm/ still needs no --profile (walk-up)`);
298
+ return;
299
+ }
220
300
  const loaded = meshLoaded(profileArg);
221
301
  const touchRegistry = async () => {
222
302
  try {
@@ -243,7 +323,7 @@ async function main() {
243
323
  sessionStatus(loaded);
244
324
  return;
245
325
  }
246
- console.error("usage: session attach|up|status");
326
+ console.error("usage: session attach|up|status|init <sm-name>");
247
327
  process.exit(2);
248
328
  }
249
329
  if (cmd === "verify") {
@@ -442,7 +522,7 @@ async function main() {
442
522
  }
443
523
  if (cmd === "to-master") {
444
524
  // FRAMEWORK-QUEUE #1 (AGENT-FUNC-GUARDS.md): to-master retired — chat-first comms.
445
- console.error('DEPRECATED: use ./sm.sh room say [-r managers] "DONE|BLOCKED|…"');
525
+ console.error('DEPRECATED: use seatmesh --profile .sm agent room say [-r managers] "DONE|BLOCKED|…"');
446
526
  process.exit(2);
447
527
  }
448
528
  if (cmd === "to-slot") {
@@ -492,12 +572,34 @@ async function main() {
492
572
  if (cmd === "peer") {
493
573
  const loaded = meshLoaded(profileArg);
494
574
  let direct = false;
575
+ let endedId;
495
576
  const args = [];
496
- for (const a of [sub, ...tail].filter((x) => x != null && x !== "")) {
497
- if (a === "--direct" || a === "--now")
577
+ const rawArgs = [sub, ...tail].filter((x) => x != null && x !== "");
578
+ for (let i = 0; i < rawArgs.length; i++) {
579
+ const a = rawArgs[i];
580
+ if (a === "--direct" || a === "--now") {
498
581
  direct = true;
499
- else
500
- args.push(a);
582
+ continue;
583
+ }
584
+ if (a === "--ended" || a === "--ack") {
585
+ const id = rawArgs[i + 1];
586
+ if (!id || id.startsWith("-")) {
587
+ console.error("usage: peer … --ended <ack-id>");
588
+ process.exit(2);
589
+ }
590
+ endedId = id;
591
+ i++;
592
+ continue;
593
+ }
594
+ if (a.startsWith("--ended=") || a.startsWith("--ack=")) {
595
+ endedId = a.slice(a.indexOf("=") + 1).trim();
596
+ if (!endedId) {
597
+ console.error("usage: peer … --ended <ack-id>");
598
+ process.exit(2);
599
+ }
600
+ continue;
601
+ }
602
+ args.push(a);
501
603
  }
502
604
  if (args[0] === "verify") {
503
605
  requireCoordRole(loaded, "peer verify");
@@ -511,13 +613,31 @@ async function main() {
511
613
  const [target, ...textParts] = args;
512
614
  const rawMsg = textParts.join(" ").trim();
513
615
  if (!target || !rawMsg) {
514
- console.error("usage: peer verify [target] | peer [--direct] <manager|secretary|slot-N|mini-N|pane> <msg...>");
616
+ console.error("usage: peer verify [target] | peer [--direct] [--ended <ack-id>] <manager|secretary|slot-N|mini-N|@alias:seat|pane> <msg...>");
515
617
  process.exit(2);
516
618
  }
619
+ const finishEnded = () => {
620
+ if (!endedId)
621
+ return;
622
+ const cfg = chatRoomConfigForLoaded(loaded);
623
+ const res = clearAckEndedSync(cfg.inboxBase, endedId, `peer -> ${target}: ${rawMsg}`);
624
+ if (!res.ok) {
625
+ console.error(`WARN: peer sent but --ended failed: ${res.error ?? "?"}`);
626
+ return;
627
+ }
628
+ console.log(`ok ended ack=${res.id ?? endedId}`);
629
+ };
517
630
  try {
631
+ const remote = parseRemotePeerTarget(target);
632
+ if (remote) {
633
+ runRemotePeer(loaded, remote.alias, remote.seat, rawMsg);
634
+ finishEnded();
635
+ return;
636
+ }
518
637
  // Workers + minis: universal peer (to-slot/to-mini/enqueue). Coords keep manager stamp.
519
638
  try {
520
639
  runPeer(loaded, target, rawMsg);
640
+ finishEnded();
521
641
  return;
522
642
  }
523
643
  catch (e) {
@@ -527,7 +647,7 @@ async function main() {
527
647
  requireCoordRole(loaded, "peer");
528
648
  if (direct) {
529
649
  const reg = createRegistryForProfile(loaded.profile);
530
- const { paneId, providerId, token, via } = injectPromptDirect(loaded, reg, target, rawMsg, { manager: true });
650
+ const { paneId, providerId, token, via } = injectPromptDirect(loaded, reg, target, rawMsg, { manager: true, force: true });
531
651
  console.log(`SENT: peer --direct -> ${target} pane=${paneId} provider=${providerId} token=${token ?? "-"} via=${via ?? "direct"}`);
532
652
  }
533
653
  else {
@@ -541,6 +661,7 @@ async function main() {
541
661
  : `SENT: peer -> ${targetLabel} pane=${paneId} token=${token ?? "-"} via=${via ?? "pane-row"}`;
542
662
  console.log(line);
543
663
  }
664
+ finishEnded();
544
665
  }
545
666
  catch (e) {
546
667
  console.error(e.message);
@@ -752,6 +873,21 @@ async function main() {
752
873
  }
753
874
  return;
754
875
  }
876
+ if (cmd === "kind" || cmd === "what" || cmd === "typeof") {
877
+ const loaded = meshLoaded(profileArg);
878
+ const reg = createRegistryForProfile(loaded.profile);
879
+ const args = [sub, ...tail].filter((a) => Boolean(a));
880
+ const json = args.includes("--json");
881
+ const target = args.find((a) => a !== "--json") ?? "here";
882
+ try {
883
+ runPaneKind(loaded, reg, target, { json });
884
+ }
885
+ catch (e) {
886
+ console.error(e.message);
887
+ process.exit(1);
888
+ }
889
+ return;
890
+ }
755
891
  if (cmd === "pane-meta") {
756
892
  const loaded = meshLoaded(profileArg);
757
893
  try {
@@ -1102,28 +1238,46 @@ async function main() {
1102
1238
  if (cmd === "agent") {
1103
1239
  const loaded = meshLoaded(profileArg);
1104
1240
  const reg = createRegistryForProfile(loaded.profile);
1105
- if (sub === "apply") {
1106
- const { bundle, opts } = parseAgentApplyArgs(tail);
1107
- applyAgentContractBundle(loaded, reg, bundle, opts);
1108
- return;
1109
- }
1110
- if (sub === "preflight") {
1111
- const { bundle, opts } = parseAgentApplyArgs(tail);
1112
- applyAgentContractBundle(loaded, reg, bundle, { ...opts, preflight: true, dryRun: true });
1113
- return;
1241
+ if (sub && AGENT_META.has(sub)) {
1242
+ if (sub === "apply") {
1243
+ const { bundle, opts } = parseAgentApplyArgs(tail);
1244
+ applyAgentContractBundle(loaded, reg, bundle, opts);
1245
+ return;
1246
+ }
1247
+ if (sub === "preflight") {
1248
+ const { bundle, opts } = parseAgentApplyArgs(tail);
1249
+ applyAgentContractBundle(loaded, reg, bundle, { ...opts, preflight: true, dryRun: true });
1250
+ return;
1251
+ }
1252
+ if (sub === "context") {
1253
+ if (tail[0] === "init") {
1254
+ const code = runAgentContextInit(loaded, { coldStartInject: tail.includes("--inject") });
1255
+ process.exit(code);
1256
+ }
1257
+ if (tail[0] === "roles") {
1258
+ process.exit(printAgentContextAllRoles(loaded));
1259
+ }
1260
+ const targetArg = tail.find((a) => !a.startsWith("-"));
1261
+ process.exit(printAgentContext(loaded, targetArg));
1262
+ }
1114
1263
  }
1115
- if (sub === "context") {
1116
- if (tail[0] === "init") {
1117
- const code = runAgentContextInit(loaded, { coldStartInject: tail.includes("--inject") });
1118
- process.exit(code);
1264
+ // Gateway: agent <cmd> … → authz then re-enter as top-level <cmd>
1265
+ if (sub && !AGENT_META.has(sub)) {
1266
+ const decision = assertAgentDispatch(loaded, sub, tail);
1267
+ if (decision.kind === "card-target") {
1268
+ const w = runWhoami(loaded, decision.target);
1269
+ printAgentCard(w);
1270
+ return;
1119
1271
  }
1120
- if (tail[0] === "roles") {
1121
- process.exit(printAgentContextAllRoles(loaded));
1272
+ if (decision.kind !== "allow") {
1273
+ printAgentUnauthorized(decision);
1274
+ process.exit(2);
1122
1275
  }
1123
- const targetArg = tail.find((a) => !a.startsWith("-"));
1124
- process.exit(printAgentContext(loaded, targetArg));
1276
+ process.argv = stripAgentFromArgv(process.argv);
1277
+ return main();
1125
1278
  }
1126
- const w = runWhoami(loaded, sub || tail[0]);
1279
+ // Bare `agent` — own card
1280
+ const w = runWhoami(loaded, "here");
1127
1281
  printAgentCard(w);
1128
1282
  return;
1129
1283
  }
@@ -1143,7 +1297,7 @@ async function main() {
1143
1297
  }
1144
1298
  if (loaded.profile.external.default === "deny") {
1145
1299
  console.error(`UNAUTHORIZED: func ${id} denied (external.default=deny)`);
1146
- console.error("hint: ./sm.sh agent");
1300
+ console.error("hint: seatmesh --profile .sm agent");
1147
1301
  process.exit(2);
1148
1302
  }
1149
1303
  try {
@@ -1153,7 +1307,7 @@ async function main() {
1153
1307
  const index = loadRoleIndex(paths.rolesDir, kind);
1154
1308
  if (!roleAllows(index.funcs, id)) {
1155
1309
  console.error(`UNAUTHORIZED: func ${id} denied for role=${kind}`);
1156
- console.error("hint: ./sm.sh agent");
1310
+ console.error("hint: seatmesh --profile .sm agent");
1157
1311
  process.exit(2);
1158
1312
  }
1159
1313
  }
@@ -1200,7 +1354,7 @@ async function main() {
1200
1354
  }
1201
1355
  if (cmd === "whoami" || cmd === "where") {
1202
1356
  if (cmd === "where") {
1203
- console.error("note: where is deprecated — use ./sm.sh whoami");
1357
+ console.error("note: where is deprecated — use seatmesh --profile .sm agent whoami");
1204
1358
  }
1205
1359
  const loaded = meshLoaded(profileArg);
1206
1360
  const json = rest.includes("--json");
@@ -1306,9 +1460,10 @@ async function main() {
1306
1460
  const prov = reg.detect(snap);
1307
1461
  const det = prov?.detect(snap);
1308
1462
  const state = prov?.composerState(snap) ?? { phase: "plain_shell" };
1463
+ const role = snap.options.mesh_role?.trim() || "plain";
1309
1464
  const slot = snap.options.mesh_slot || "-";
1310
1465
  const ports = snap.options.mesh_ports || "-";
1311
- console.log(`${paneId}\t${prov?.id ?? "?"}\t${det?.resumeId ?? "-"}\t${state.phase}${state.limitKind ? `:${state.limitKind}` : ""}\tslot=${slot}\tports=${ports}\t${snap.windowName}`);
1466
+ console.log(`${paneId}\trole=${role}\t${prov?.id ?? "?"}\t${det?.resumeId ?? "-"}\t${state.phase}${state.limitKind ? `:${state.limitKind}` : ""}\tslot=${slot}\tports=${ports}\t${snap.windowName}`);
1312
1467
  }
1313
1468
  return;
1314
1469
  }
@@ -1389,7 +1544,7 @@ async function main() {
1389
1544
  }
1390
1545
  return;
1391
1546
  }
1392
- if (cmd === "checkback" || cmd === "patience") {
1547
+ if (cmd === "checkback" || cmd === "patience" || cmd === "cb") {
1393
1548
  const loaded = meshLoaded(profileArg);
1394
1549
  const getLoaded = () => loaded;
1395
1550
  const branch = buildCheckbackCommands(getLoaded);
@@ -1408,6 +1563,40 @@ async function main() {
1408
1563
  }
1409
1564
  return;
1410
1565
  }
1566
+ if (cmd === "target" || cmd === "targets") {
1567
+ const loaded = meshLoaded(profileArg);
1568
+ const getLoaded = () => loaded;
1569
+ const branch = buildTargetCommands(getLoaded);
1570
+ if (!sub) {
1571
+ branch.outputHelp();
1572
+ return;
1573
+ }
1574
+ try {
1575
+ await branch.parseAsync([sub, ...tail], { from: "user" });
1576
+ }
1577
+ catch (e) {
1578
+ const err = e;
1579
+ if (err.code === "commander.helpDisplayed" || err.code === "commander.version")
1580
+ return;
1581
+ throw e;
1582
+ }
1583
+ return;
1584
+ }
1585
+ if (cmd === "ack") {
1586
+ const loaded = meshLoaded(profileArg);
1587
+ const getLoaded = () => loaded;
1588
+ const branch = buildAckCommands(getLoaded);
1589
+ try {
1590
+ await branch.parseAsync(rest.slice(1), { from: "user" });
1591
+ }
1592
+ catch (e) {
1593
+ const err = e;
1594
+ if (err.code === "commander.helpDisplayed" || err.code === "commander.version")
1595
+ return;
1596
+ throw e;
1597
+ }
1598
+ return;
1599
+ }
1411
1600
  if (cmd === "notify") {
1412
1601
  const loaded = meshLoaded(profileArg);
1413
1602
  const getLoaded = () => loaded;