@mattstack/rt-client 0.16.0 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +33 -0
- package/dist/commands.d.ts +305 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +206 -17
- package/dist/pane-ref.d.ts +8 -0
- package/dist/settings/paths.d.ts +8 -0
- package/dist/settings/team-local-read.d.ts +7 -0
- package/package.json +1 -1
- package/src/client.ts +163 -4
- package/src/commands.ts +90 -8
- package/src/index.ts +25 -0
- package/src/pane-ref.ts +33 -0
- package/src/settings/paths.ts +11 -0
- package/src/settings/registry-defs.ts +13 -3
- package/src/settings/team-local-read.ts +18 -0
- package/src/settings/write.ts +24 -3
package/dist/index.js
CHANGED
|
@@ -205,7 +205,7 @@ function eventsList(payload, o = {}) {
|
|
|
205
205
|
}
|
|
206
206
|
function agentStart(a, o = {}) {
|
|
207
207
|
const payload = { repo: a.repo, cwd: a.cwd };
|
|
208
|
-
for (const k of ["prompt", "surface", "model", "effort", "account", "label", "caller", "workspace", "tab", "extraArgs"]) {
|
|
208
|
+
for (const k of ["prompt", "surface", "model", "effort", "account", "label", "caller", "workspace", "tab", "extraArgs", "env", "herdrSocket", "handle", "bg"]) {
|
|
209
209
|
if (a[k] !== undefined)
|
|
210
210
|
payload[k] = a[k];
|
|
211
211
|
}
|
|
@@ -268,7 +268,10 @@ function paneSend(a, o = {}) {
|
|
|
268
268
|
return rtCommand("pane:send", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30000 });
|
|
269
269
|
}
|
|
270
270
|
function paneFocus(a, o = {}) {
|
|
271
|
-
|
|
271
|
+
const payload = { paneId: a.paneId };
|
|
272
|
+
if (a.callerWorkspace !== undefined)
|
|
273
|
+
payload.callerWorkspace = a.callerWorkspace;
|
|
274
|
+
return rtCommand("pane:focus", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
272
275
|
}
|
|
273
276
|
function gateOpen(a, o = {}) {
|
|
274
277
|
const payload = { subject: a.subject, kind: a.kind, questions: a.questions };
|
|
@@ -278,7 +281,11 @@ function gateOpen(a, o = {}) {
|
|
|
278
281
|
return rtCommand("gate:open", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
279
282
|
}
|
|
280
283
|
function gateAnswer(a, o = {}) {
|
|
281
|
-
|
|
284
|
+
const payload = { id: a.id, answers: a.answers, by: a.by };
|
|
285
|
+
for (const k of ["session", "override"])
|
|
286
|
+
if (a[k] !== undefined)
|
|
287
|
+
payload[k] = a[k];
|
|
288
|
+
return rtCommand("gate:answer", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
282
289
|
}
|
|
283
290
|
function gateWait(a, o = {}) {
|
|
284
291
|
const payload = { id: a.id };
|
|
@@ -300,7 +307,11 @@ function gateClose(a, o = {}) {
|
|
|
300
307
|
return rtCommand("gate:close", { id: a.id, reason: a.reason }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
301
308
|
}
|
|
302
309
|
function gateSubscribe(a, o = {}) {
|
|
303
|
-
|
|
310
|
+
const payload = { subjectPrefix: a.subjectPrefix, session: a.session };
|
|
311
|
+
for (const k of ["scope", "ownerRef"])
|
|
312
|
+
if (a[k] !== undefined)
|
|
313
|
+
payload[k] = a[k];
|
|
314
|
+
return rtCommand("gate:subscribe", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
304
315
|
}
|
|
305
316
|
function gateUnsubscribe(a, o = {}) {
|
|
306
317
|
return rtCommand("gate:unsubscribe", { id: a.id }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
@@ -312,6 +323,85 @@ function gateSubscriptions(a, o = {}) {
|
|
|
312
323
|
payload[k] = a[k];
|
|
313
324
|
return rtCommand("gate:subscriptions", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
314
325
|
}
|
|
326
|
+
function herdStart(a, o = {}) {
|
|
327
|
+
const payload = { name: a.name, repo: a.repo, session: a.session };
|
|
328
|
+
if (a.hidden !== undefined)
|
|
329
|
+
payload.hidden = a.hidden;
|
|
330
|
+
return rtCommand("herd:start", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 60000 });
|
|
331
|
+
}
|
|
332
|
+
function herdSpawn(a, o = {}) {
|
|
333
|
+
const payload = { herd: a.herd, job: a.job };
|
|
334
|
+
for (const k of ["brief", "dir", "model", "effort", "account", "disposable"])
|
|
335
|
+
if (a[k] !== undefined)
|
|
336
|
+
payload[k] = a[k];
|
|
337
|
+
return rtCommand("herd:spawn", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 60000 });
|
|
338
|
+
}
|
|
339
|
+
function herdAsk(a, o = {}) {
|
|
340
|
+
const payload = { herd: a.herd, job: a.job, session: a.session, questions: a.questions };
|
|
341
|
+
for (const k of ["pane", "context"])
|
|
342
|
+
if (a[k] !== undefined)
|
|
343
|
+
payload[k] = a[k];
|
|
344
|
+
return rtCommand("herd:ask", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
345
|
+
}
|
|
346
|
+
function herdMilestone(a, o = {}) {
|
|
347
|
+
const payload = { herd: a.herd, job: a.job, session: a.session, artifact: a.artifact };
|
|
348
|
+
for (const k of ["pane", "summary"])
|
|
349
|
+
if (a[k] !== undefined)
|
|
350
|
+
payload[k] = a[k];
|
|
351
|
+
return rtCommand("herd:milestone", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
352
|
+
}
|
|
353
|
+
function herdAnswer(a, o = {}) {
|
|
354
|
+
return rtCommand("herd:answer", { gate: a.gate }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
355
|
+
}
|
|
356
|
+
function herdReport(a, o = {}) {
|
|
357
|
+
return rtCommand("herd:report", { herd: a.herd, job: a.job, body: a.body }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30000 });
|
|
358
|
+
}
|
|
359
|
+
function herdGates(a, o = {}) {
|
|
360
|
+
return rtCommand("herd:gates", { herd: a.herd }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
361
|
+
}
|
|
362
|
+
function herdStatus(a, o = {}) {
|
|
363
|
+
return rtCommand("herd:status", { herd: a.herd }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
364
|
+
}
|
|
365
|
+
function herdList(a = {}, o = {}) {
|
|
366
|
+
const payload = {};
|
|
367
|
+
if (a.all !== undefined)
|
|
368
|
+
payload.all = a.all;
|
|
369
|
+
return rtCommand("herd:list", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
370
|
+
}
|
|
371
|
+
function herdResume(a, o = {}) {
|
|
372
|
+
return rtCommand("herd:resume", { herd: a.herd, session: a.session }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
373
|
+
}
|
|
374
|
+
function herdClose(a, o = {}) {
|
|
375
|
+
return rtCommand("herd:close", { herd: a.herd, job: a.job }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30000 });
|
|
376
|
+
}
|
|
377
|
+
function herdAttend(a, o = {}) {
|
|
378
|
+
return rtCommand("herd:attend", { herd: a.herd, job: a.job, callerWorkspace: a.callerWorkspace }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 60000 });
|
|
379
|
+
}
|
|
380
|
+
function herdWrapUp(a, o = {}) {
|
|
381
|
+
const payload = { herd: a.herd };
|
|
382
|
+
for (const k of ["closePanes", "dispose", "deleteJobDirs", "archiveRoom"])
|
|
383
|
+
if (a[k] !== undefined)
|
|
384
|
+
payload[k] = a[k];
|
|
385
|
+
return rtCommand("herd:wrap-up", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 120000 });
|
|
386
|
+
}
|
|
387
|
+
function herdStopHidden(_a, o = {}) {
|
|
388
|
+
return rtCommand("herd:stop-hidden", {}, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30000 });
|
|
389
|
+
}
|
|
390
|
+
function bgEnsure(a = {}, o = {}) {
|
|
391
|
+
const payload = {};
|
|
392
|
+
if (a.claim !== undefined)
|
|
393
|
+
payload.claim = a.claim;
|
|
394
|
+
return rtCommand("bg:ensure", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30000 });
|
|
395
|
+
}
|
|
396
|
+
function bgStatus(o = {}) {
|
|
397
|
+
return rtCommand("bg:status", {}, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
398
|
+
}
|
|
399
|
+
function bgStop(o = {}) {
|
|
400
|
+
return rtCommand("bg:stop", {}, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30000 });
|
|
401
|
+
}
|
|
402
|
+
function bgRelease(a, o = {}) {
|
|
403
|
+
return rtCommand("bg:release", { claim: a.claim }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
404
|
+
}
|
|
315
405
|
// src/commands.ts
|
|
316
406
|
var GATE_BY_PANE = "pane";
|
|
317
407
|
function gateOptionValue(o) {
|
|
@@ -397,6 +487,20 @@ var COMMAND_NAMES = [
|
|
|
397
487
|
"gate:subscribe",
|
|
398
488
|
"gate:unsubscribe",
|
|
399
489
|
"gate:subscriptions",
|
|
490
|
+
"herd:start",
|
|
491
|
+
"herd:resume",
|
|
492
|
+
"herd:status",
|
|
493
|
+
"herd:list",
|
|
494
|
+
"herd:close",
|
|
495
|
+
"herd:spawn",
|
|
496
|
+
"herd:gates",
|
|
497
|
+
"herd:ask",
|
|
498
|
+
"herd:milestone",
|
|
499
|
+
"herd:answer",
|
|
500
|
+
"herd:report",
|
|
501
|
+
"herd:attend",
|
|
502
|
+
"herd:stop-hidden",
|
|
503
|
+
"herd:wrap-up",
|
|
400
504
|
"hooks:repair",
|
|
401
505
|
"hooks:watch",
|
|
402
506
|
"sdm:catalog",
|
|
@@ -410,7 +514,11 @@ var COMMAND_NAMES = [
|
|
|
410
514
|
"worktree:list",
|
|
411
515
|
"worktree:restore",
|
|
412
516
|
"worktree:freshen",
|
|
413
|
-
"worktree:adopt"
|
|
517
|
+
"worktree:adopt",
|
|
518
|
+
"bg:ensure",
|
|
519
|
+
"bg:status",
|
|
520
|
+
"bg:stop",
|
|
521
|
+
"bg:release"
|
|
414
522
|
];
|
|
415
523
|
// src/relay.ts
|
|
416
524
|
var DEFAULT_WS_URL = "ws://127.0.0.1:9401/ws";
|
|
@@ -587,6 +695,29 @@ async function openSmartPane(herdr, anchorPaneId, opts = {}) {
|
|
|
587
695
|
}
|
|
588
696
|
return { paneId, placement };
|
|
589
697
|
}
|
|
698
|
+
// src/pane-ref.ts
|
|
699
|
+
var BG_PREFIX = "bg:";
|
|
700
|
+
function parsePaneRef(ref) {
|
|
701
|
+
if (ref.startsWith(BG_PREFIX)) {
|
|
702
|
+
return {
|
|
703
|
+
server: "bg",
|
|
704
|
+
paneId: ref.slice(BG_PREFIX.length)
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
return {
|
|
708
|
+
server: "visible",
|
|
709
|
+
paneId: ref
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
function formatPaneRef(paneId, server) {
|
|
713
|
+
if (server === "visible") {
|
|
714
|
+
return paneId;
|
|
715
|
+
}
|
|
716
|
+
if (paneId.startsWith(BG_PREFIX)) {
|
|
717
|
+
return paneId;
|
|
718
|
+
}
|
|
719
|
+
return BG_PREFIX + paneId;
|
|
720
|
+
}
|
|
590
721
|
// src/settings/resolve.ts
|
|
591
722
|
import { homedir as homedir4 } from "os";
|
|
592
723
|
import { join as join5 } from "path";
|
|
@@ -604,6 +735,9 @@ function userSettingsPath() {
|
|
|
604
735
|
function teamSettingsPath(team) {
|
|
605
736
|
return join3(teamsDir(), team, "mattstack", "settings.team.jsonc");
|
|
606
737
|
}
|
|
738
|
+
function teamLocalPath(team) {
|
|
739
|
+
return join3(home(), ".mattstack", "rt", "teams", `${team}.json`);
|
|
740
|
+
}
|
|
607
741
|
function machineSettingsPath() {
|
|
608
742
|
return join3(home(), ".mattstack", "user", "local", machineKey(), "settings.local.jsonc");
|
|
609
743
|
}
|
|
@@ -654,7 +788,7 @@ var REGISTRY = [
|
|
|
654
788
|
merge: "deep",
|
|
655
789
|
repoScoped: true,
|
|
656
790
|
migrated: true,
|
|
657
|
-
description: "Per-repo worktree pool config (onDeck size, ready steps, name pool); root/branchFormat/ready computed-or-empty in the reader."
|
|
791
|
+
description: "Per-repo worktree pool config (onDeck size, ready steps, name pool, staleClaimDays); root/branchFormat/ready computed-or-empty in the reader."
|
|
658
792
|
},
|
|
659
793
|
{
|
|
660
794
|
key: "rt.worktreeReadyApproval",
|
|
@@ -696,7 +830,7 @@ var REGISTRY = [
|
|
|
696
830
|
scopes: ["user"],
|
|
697
831
|
default: [],
|
|
698
832
|
merge: "replace",
|
|
699
|
-
description:
|
|
833
|
+
description: 'Event-bus glob rules that raise a desktop notification: [{pattern, category, title, message, subjectPrefix?, url?, owner?}]. pattern is matched against the events-bus topic (Bun.Glob semantics); title/message may interpolate `{field}` from the event payload, plus the computed `{question}` field (the event payload\'s first question label, `payload.questions[0].label`, empty string when absent); optional subjectPrefix matches the event payload\'s subject as a prefix. The optional url is interpolated the same way as title/message and becomes the notification\'s Open target; a gate rule should set it. The optional owner field (only literal "human" is valid) suppresses events whose payload.owner starts with "herd:", allowing gate rules to skip herd-owned events. A typical setup pairs a gate/opened rule with owner: "human" (human-owned gates notify) and a gate/escalated rule without owner (all escalations notify, whether human or herd). A fresh key, not an ownership-latch port, so a default is fine here.'
|
|
700
834
|
},
|
|
701
835
|
{
|
|
702
836
|
key: "rt.cron",
|
|
@@ -1015,7 +1149,7 @@ var REGISTRY = [
|
|
|
1015
1149
|
type: "array",
|
|
1016
1150
|
scopes: ["team"],
|
|
1017
1151
|
merge: "replace",
|
|
1018
|
-
description: "Board tabs ({id, label, source, slackChannel?, reviewSkill?}), editable from the board's settings modal. source.kind 'authors' is the classic roster board; 'codeowners' lists MRs
|
|
1152
|
+
description: "Board tabs ({id, label, source, slackChannel?, reviewSkill?}), editable from the board's settings modal. source.kind 'authors' is the classic roster board; 'codeowners' lists any author's open MRs carrying a CODE_OWNER rule for the section, until merge/close. Absent = one implicit authors tab (fallback lives in the board reader, never here)."
|
|
1019
1153
|
},
|
|
1020
1154
|
{
|
|
1021
1155
|
key: "board.staleAfterDays",
|
|
@@ -1258,6 +1392,14 @@ var REGISTRY = [
|
|
|
1258
1392
|
scopes: ["user", "machine"],
|
|
1259
1393
|
merge: "replace",
|
|
1260
1394
|
description: "Opaque extra claude arguments appended to every rt agent launch (escape hatch)."
|
|
1395
|
+
},
|
|
1396
|
+
{
|
|
1397
|
+
key: "rt.gates.escalationTtlMinutes",
|
|
1398
|
+
type: "number",
|
|
1399
|
+
scopes: ["user"],
|
|
1400
|
+
default: 10,
|
|
1401
|
+
merge: "replace",
|
|
1402
|
+
description: `Minutes an open herd-owned gate waits before the escalation sweep surfaces it to the human (topic gate/escalated/<id>). Fires on either trigger: the TTL elapses (reason "ttl"), or the owning herd's shepherd subscription is gone or dead before the TTL (reason "owner-dead"). 0 escalates any eligible gate on the first sweep after it opens. A fresh key, not an ownership-latch port, so a default is fine here.`
|
|
1261
1403
|
}
|
|
1262
1404
|
];
|
|
1263
1405
|
|
|
@@ -1712,10 +1854,23 @@ function explainSetting(key, opts = {}) {
|
|
|
1712
1854
|
return resolveDef(def, readStores(), opts).rows;
|
|
1713
1855
|
}
|
|
1714
1856
|
// src/settings/write.ts
|
|
1715
|
-
import { existsSync as existsSync3, mkdirSync, readFileSync as
|
|
1857
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync5, renameSync, unlinkSync, writeFileSync } from "fs";
|
|
1716
1858
|
import { applyEdits, modify, parseTree } from "jsonc-parser";
|
|
1717
1859
|
import { randomBytes } from "crypto";
|
|
1718
1860
|
import { dirname as dirname2 } from "path";
|
|
1861
|
+
|
|
1862
|
+
// src/settings/team-local-read.ts
|
|
1863
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
1864
|
+
function isJoinedTeam(team) {
|
|
1865
|
+
try {
|
|
1866
|
+
const parsed = JSON.parse(readFileSync4(teamLocalPath(team), "utf8"));
|
|
1867
|
+
return typeof parsed === "object" && parsed !== null && parsed.joinedByRt === true;
|
|
1868
|
+
} catch {
|
|
1869
|
+
return false;
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
// src/settings/write.ts
|
|
1719
1874
|
var FORMAT = { tabSize: 2, insertSpaces: true, eol: `
|
|
1720
1875
|
` };
|
|
1721
1876
|
function refuse(message) {
|
|
@@ -1773,6 +1928,11 @@ function migratedFalseMessage(key, def) {
|
|
|
1773
1928
|
const legacyPart = def.legacyFile ? ` — it is still read from ${def.legacyFile}` : "";
|
|
1774
1929
|
return `"${key}" is not writable through the settings resolver yet${legacyPart}`;
|
|
1775
1930
|
}
|
|
1931
|
+
function refuseIfJoined(team) {
|
|
1932
|
+
if (isJoinedTeam(team)) {
|
|
1933
|
+
refuse(`this machine joined "${team}" by invite, so its clone is pull-only and team settings cannot be written here. Ask the team's owner to make this change. Member-proposed changes are tracked in MAT-415.`);
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1776
1936
|
function resolveStorePath(scope, opts) {
|
|
1777
1937
|
if (scope === "user")
|
|
1778
1938
|
return userSettingsPath();
|
|
@@ -1783,6 +1943,7 @@ function resolveStorePath(scope, opts) {
|
|
|
1783
1943
|
if (!existsSync3(path)) {
|
|
1784
1944
|
refuse(`team store for "${opts.team}" does not exist (${path}) — clone/seed it before writing to it`);
|
|
1785
1945
|
}
|
|
1946
|
+
refuseIfJoined(opts.team);
|
|
1786
1947
|
return path;
|
|
1787
1948
|
}
|
|
1788
1949
|
const teams = listTeams();
|
|
@@ -1792,7 +1953,9 @@ function resolveStorePath(scope, opts) {
|
|
|
1792
1953
|
if (teams.length > 1) {
|
|
1793
1954
|
refuse(`multiple local team stores found (${teams.join(", ")}) — pass opts.team to choose one`);
|
|
1794
1955
|
}
|
|
1795
|
-
|
|
1956
|
+
const team = teams[0];
|
|
1957
|
+
refuseIfJoined(team);
|
|
1958
|
+
return teamSettingsPath(team);
|
|
1796
1959
|
}
|
|
1797
1960
|
function resolveStorePathForUnset(scope, opts) {
|
|
1798
1961
|
if (scope === "user")
|
|
@@ -1801,7 +1964,10 @@ function resolveStorePathForUnset(scope, opts) {
|
|
|
1801
1964
|
return machineSettingsPath();
|
|
1802
1965
|
if (opts.team !== undefined) {
|
|
1803
1966
|
const path = teamSettingsPath(opts.team);
|
|
1804
|
-
|
|
1967
|
+
if (!existsSync3(path))
|
|
1968
|
+
return null;
|
|
1969
|
+
refuseIfJoined(opts.team);
|
|
1970
|
+
return path;
|
|
1805
1971
|
}
|
|
1806
1972
|
const teams = listTeams();
|
|
1807
1973
|
if (teams.length === 0)
|
|
@@ -1809,7 +1975,9 @@ function resolveStorePathForUnset(scope, opts) {
|
|
|
1809
1975
|
if (teams.length > 1) {
|
|
1810
1976
|
refuse(`multiple local team stores found (${teams.join(", ")}) — pass opts.team to choose one`);
|
|
1811
1977
|
}
|
|
1812
|
-
|
|
1978
|
+
const team = teams[0];
|
|
1979
|
+
refuseIfJoined(team);
|
|
1980
|
+
return teamSettingsPath(team);
|
|
1813
1981
|
}
|
|
1814
1982
|
function seedHeader() {
|
|
1815
1983
|
return `// rt settings — created by \`rt settings set\`. JSONC: comments and trailing commas are fine.
|
|
@@ -1855,7 +2023,7 @@ function findDuplicateKey(node) {
|
|
|
1855
2023
|
function writeIntoStore(storePath, jsonPath, value, createIfMissing) {
|
|
1856
2024
|
let content;
|
|
1857
2025
|
if (existsSync3(storePath)) {
|
|
1858
|
-
content =
|
|
2026
|
+
content = readFileSync5(storePath, "utf8");
|
|
1859
2027
|
if (content.trim() === "") {
|
|
1860
2028
|
content = seedHeader();
|
|
1861
2029
|
} else {
|
|
@@ -1876,7 +2044,7 @@ function writeIntoStore(storePath, jsonPath, value, createIfMissing) {
|
|
|
1876
2044
|
writeTempThenRename(storePath, finalText);
|
|
1877
2045
|
}
|
|
1878
2046
|
function removeFromStore(storePath, jsonPath) {
|
|
1879
|
-
const content =
|
|
2047
|
+
const content = readFileSync5(storePath, "utf8");
|
|
1880
2048
|
if (content.trim() === "")
|
|
1881
2049
|
return false;
|
|
1882
2050
|
assertEditableJsonc(storePath, content);
|
|
@@ -1903,7 +2071,7 @@ function writeTempThenRename(storePath, finalText) {
|
|
|
1903
2071
|
}
|
|
1904
2072
|
}
|
|
1905
2073
|
// src/settings/identity.ts
|
|
1906
|
-
import { existsSync as existsSync4, readFileSync as
|
|
2074
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6, realpathSync } from "fs";
|
|
1907
2075
|
|
|
1908
2076
|
// src/settings/exec.ts
|
|
1909
2077
|
async function runCapture(argv, opts = {}) {
|
|
@@ -2065,7 +2233,7 @@ async function resolveNameToIdentity(name, reposJsonPath) {
|
|
|
2065
2233
|
if (!existsSync4(reposJsonPath))
|
|
2066
2234
|
return null;
|
|
2067
2235
|
try {
|
|
2068
|
-
const index = JSON.parse(
|
|
2236
|
+
const index = JSON.parse(readFileSync6(reposJsonPath, "utf8"));
|
|
2069
2237
|
const path = index[name];
|
|
2070
2238
|
if (typeof path !== "string")
|
|
2071
2239
|
return null;
|
|
@@ -2090,6 +2258,7 @@ export {
|
|
|
2090
2258
|
readMrsByBranch,
|
|
2091
2259
|
readDiscussions,
|
|
2092
2260
|
readBranchCache,
|
|
2261
|
+
parsePaneRef,
|
|
2093
2262
|
parseIdentity,
|
|
2094
2263
|
paneSpawn,
|
|
2095
2264
|
paneSend,
|
|
@@ -2105,6 +2274,20 @@ export {
|
|
|
2105
2274
|
listRuns,
|
|
2106
2275
|
isMigrated,
|
|
2107
2276
|
identityFromRemote,
|
|
2277
|
+
herdWrapUp,
|
|
2278
|
+
herdStopHidden,
|
|
2279
|
+
herdStatus,
|
|
2280
|
+
herdStart,
|
|
2281
|
+
herdSpawn,
|
|
2282
|
+
herdResume,
|
|
2283
|
+
herdReport,
|
|
2284
|
+
herdMilestone,
|
|
2285
|
+
herdList,
|
|
2286
|
+
herdGates,
|
|
2287
|
+
herdClose,
|
|
2288
|
+
herdAttend,
|
|
2289
|
+
herdAsk,
|
|
2290
|
+
herdAnswer,
|
|
2108
2291
|
getSetting,
|
|
2109
2292
|
getRun,
|
|
2110
2293
|
getDef,
|
|
@@ -2119,6 +2302,7 @@ export {
|
|
|
2119
2302
|
gateList,
|
|
2120
2303
|
gateClose,
|
|
2121
2304
|
gateAnswer,
|
|
2305
|
+
formatPaneRef,
|
|
2122
2306
|
explainSetting,
|
|
2123
2307
|
expandVariables,
|
|
2124
2308
|
eventsWait,
|
|
@@ -2150,6 +2334,10 @@ export {
|
|
|
2150
2334
|
chatAway,
|
|
2151
2335
|
chatArchive,
|
|
2152
2336
|
chatAck,
|
|
2337
|
+
bgStop,
|
|
2338
|
+
bgStatus,
|
|
2339
|
+
bgRelease,
|
|
2340
|
+
bgEnsure,
|
|
2153
2341
|
allDefs,
|
|
2154
2342
|
agentStart,
|
|
2155
2343
|
agentResume,
|
|
@@ -2161,5 +2349,6 @@ export {
|
|
|
2161
2349
|
GATE_BY_PANE,
|
|
2162
2350
|
DEFAULT_WS_URL,
|
|
2163
2351
|
DEFAULT_SOCK,
|
|
2164
|
-
COMMAND_NAMES
|
|
2352
|
+
COMMAND_NAMES,
|
|
2353
|
+
BG_PREFIX
|
|
2165
2354
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const BG_PREFIX = "bg:";
|
|
2
|
+
export type PaneServer = "visible" | "bg";
|
|
3
|
+
export interface PaneRef {
|
|
4
|
+
server: PaneServer;
|
|
5
|
+
paneId: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function parsePaneRef(ref: string): PaneRef;
|
|
8
|
+
export declare function formatPaneRef(paneId: string, server: PaneServer): string;
|
package/dist/settings/paths.d.ts
CHANGED
|
@@ -12,6 +12,14 @@
|
|
|
12
12
|
export declare function userSettingsPath(): string;
|
|
13
13
|
/** ~/.mattstack/teams/<team>/mattstack/settings.team.jsonc — the team store. */
|
|
14
14
|
export declare function teamSettingsPath(team: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* ~/.mattstack/rt/teams/<team>.json: the machine-local team record. Mirrored
|
|
17
|
+
* from repo-tools/lib/team/team-local.ts's teamLocalPath, which is the
|
|
18
|
+
* authority: rt-client has no dependency on rt's lib/, so this literal is
|
|
19
|
+
* duplicated here rather than imported (same convention as `teamSettingsPath`
|
|
20
|
+
* and `userSettingsPath` above).
|
|
21
|
+
*/
|
|
22
|
+
export declare function teamLocalPath(team: string): string;
|
|
15
23
|
/**
|
|
16
24
|
* ~/.mattstack/user/local/<machineKey()>/settings.local.jsonc — the machine
|
|
17
25
|
* store, TRACKED and keyed per machine (path literals legal here only).
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One field of the machine-local team record, for the write guard. The record
|
|
3
|
+
* itself is owned by repo-tools/lib/team/team-local.ts; this reads only what
|
|
4
|
+
* the guard needs and never writes.
|
|
5
|
+
*/
|
|
6
|
+
/** Unreadable, absent or malformed all read as false, so nothing that predates the field is refused. */
|
|
7
|
+
export declare function isJoinedTeam(team: string): boolean;
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -356,7 +356,7 @@ export function agentStart(
|
|
|
356
356
|
a: Commands["agent:start"]["payload"], o: RtClientOptions = {},
|
|
357
357
|
): Promise<RtResponse<AgentRecord>> {
|
|
358
358
|
const payload: Record<string, unknown> = { repo: a.repo, cwd: a.cwd };
|
|
359
|
-
for (const k of ["prompt", "surface", "model", "effort", "account", "label", "caller", "workspace", "tab", "extraArgs"] as const) {
|
|
359
|
+
for (const k of ["prompt", "surface", "model", "effort", "account", "label", "caller", "workspace", "tab", "extraArgs", "env", "herdrSocket", "handle", "bg"] as const) {
|
|
360
360
|
if (a[k] !== undefined) payload[k] = a[k];
|
|
361
361
|
}
|
|
362
362
|
return rtCommand<AgentRecord>("agent:start", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30_000 });
|
|
@@ -441,7 +441,9 @@ export function paneFocus(
|
|
|
441
441
|
a: Commands["pane:focus"]["payload"],
|
|
442
442
|
o: RtClientOptions = {},
|
|
443
443
|
): Promise<RtResponse<Commands["pane:focus"]["data"]>> {
|
|
444
|
-
|
|
444
|
+
const payload: Record<string, unknown> = { paneId: a.paneId };
|
|
445
|
+
if (a.callerWorkspace !== undefined) payload.callerWorkspace = a.callerWorkspace;
|
|
446
|
+
return rtCommand<Commands["pane:focus"]["data"]>("pane:focus", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
445
447
|
}
|
|
446
448
|
|
|
447
449
|
// ─── Gates (BOARD-20/21 gate facility) ─────────────────────────────────────
|
|
@@ -459,7 +461,9 @@ export function gateAnswer(
|
|
|
459
461
|
a: Commands["gate:answer"]["payload"],
|
|
460
462
|
o: RtClientOptions = {},
|
|
461
463
|
): Promise<RtResponse<Commands["gate:answer"]["data"]>> {
|
|
462
|
-
|
|
464
|
+
const payload: Record<string, unknown> = { id: a.id, answers: a.answers, by: a.by };
|
|
465
|
+
for (const k of ["session", "override"] as const) if (a[k] !== undefined) payload[k] = a[k];
|
|
466
|
+
return rtCommand<Commands["gate:answer"]["data"]>("gate:answer", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
463
467
|
}
|
|
464
468
|
|
|
465
469
|
/** Daemon clamps its own wait to 240s (gates-store.ts); the client abort
|
|
@@ -501,7 +505,9 @@ export function gateSubscribe(
|
|
|
501
505
|
a: Commands["gate:subscribe"]["payload"],
|
|
502
506
|
o: RtClientOptions = {},
|
|
503
507
|
): Promise<RtResponse<Commands["gate:subscribe"]["data"]>> {
|
|
504
|
-
|
|
508
|
+
const payload: Record<string, unknown> = { subjectPrefix: a.subjectPrefix, session: a.session };
|
|
509
|
+
for (const k of ["scope", "ownerRef"] as const) if (a[k] !== undefined) payload[k] = a[k];
|
|
510
|
+
return rtCommand<Commands["gate:subscribe"]["data"]>("gate:subscribe", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
505
511
|
}
|
|
506
512
|
|
|
507
513
|
export function gateUnsubscribe(
|
|
@@ -519,3 +525,156 @@ export function gateSubscriptions(
|
|
|
519
525
|
for (const k of ["session", "live"] as const) if (a[k] !== undefined) payload[k] = a[k];
|
|
520
526
|
return rtCommand<Commands["gate:subscriptions"]["data"]>("gate:subscriptions", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
521
527
|
}
|
|
528
|
+
|
|
529
|
+
// ─── Herd (shepherd run registry) ───────────────────────────────────────────
|
|
530
|
+
|
|
531
|
+
/** Provisions the room, workspace and subscription before it answers, so it
|
|
532
|
+
gets the same 60s budget as a spawn rather than the 10s default. */
|
|
533
|
+
export function herdStart(
|
|
534
|
+
a: Commands["herd:start"]["payload"],
|
|
535
|
+
o: RtClientOptions = {},
|
|
536
|
+
): Promise<RtResponse<Commands["herd:start"]["data"]>> {
|
|
537
|
+
const payload: Record<string, unknown> = { name: a.name, repo: a.repo, session: a.session };
|
|
538
|
+
if (a.hidden !== undefined) payload.hidden = a.hidden;
|
|
539
|
+
return rtCommand<Commands["herd:start"]["data"]>("herd:start", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 60_000 });
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/** Worktree provision plus agent launch; the 10s default cannot cover it. */
|
|
543
|
+
export function herdSpawn(
|
|
544
|
+
a: Commands["herd:spawn"]["payload"],
|
|
545
|
+
o: RtClientOptions = {},
|
|
546
|
+
): Promise<RtResponse<Commands["herd:spawn"]["data"]>> {
|
|
547
|
+
const payload: Record<string, unknown> = { herd: a.herd, job: a.job };
|
|
548
|
+
for (const k of ["brief", "dir", "model", "effort", "account", "disposable"] as const) if (a[k] !== undefined) payload[k] = a[k];
|
|
549
|
+
return rtCommand<Commands["herd:spawn"]["data"]>("herd:spawn", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 60_000 });
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
export function herdAsk(
|
|
553
|
+
a: Commands["herd:ask"]["payload"],
|
|
554
|
+
o: RtClientOptions = {},
|
|
555
|
+
): Promise<RtResponse<Commands["herd:ask"]["data"]>> {
|
|
556
|
+
const payload: Record<string, unknown> = { herd: a.herd, job: a.job, session: a.session, questions: a.questions };
|
|
557
|
+
for (const k of ["pane", "context"] as const) if (a[k] !== undefined) payload[k] = a[k];
|
|
558
|
+
return rtCommand<Commands["herd:ask"]["data"]>("herd:ask", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
export function herdMilestone(
|
|
562
|
+
a: Commands["herd:milestone"]["payload"],
|
|
563
|
+
o: RtClientOptions = {},
|
|
564
|
+
): Promise<RtResponse<Commands["herd:milestone"]["data"]>> {
|
|
565
|
+
const payload: Record<string, unknown> = { herd: a.herd, job: a.job, session: a.session, artifact: a.artifact };
|
|
566
|
+
for (const k of ["pane", "summary"] as const) if (a[k] !== undefined) payload[k] = a[k];
|
|
567
|
+
return rtCommand<Commands["herd:milestone"]["data"]>("herd:milestone", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
export function herdAnswer(
|
|
571
|
+
a: Commands["herd:answer"]["payload"],
|
|
572
|
+
o: RtClientOptions = {},
|
|
573
|
+
): Promise<RtResponse<Commands["herd:answer"]["data"]>> {
|
|
574
|
+
return rtCommand<Commands["herd:answer"]["data"]>("herd:answer", { gate: a.gate }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/** A disposable job's report also closes its pane, one herdr CLI call under the runner's own 15s budget. */
|
|
578
|
+
export function herdReport(
|
|
579
|
+
a: Commands["herd:report"]["payload"],
|
|
580
|
+
o: RtClientOptions = {},
|
|
581
|
+
): Promise<RtResponse<Commands["herd:report"]["data"]>> {
|
|
582
|
+
return rtCommand<Commands["herd:report"]["data"]>("herd:report", { herd: a.herd, job: a.job, body: a.body }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30_000 });
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
export function herdGates(
|
|
586
|
+
a: Commands["herd:gates"]["payload"],
|
|
587
|
+
o: RtClientOptions = {},
|
|
588
|
+
): Promise<RtResponse<Commands["herd:gates"]["data"]>> {
|
|
589
|
+
return rtCommand<Commands["herd:gates"]["data"]>("herd:gates", { herd: a.herd }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
export function herdStatus(
|
|
593
|
+
a: Commands["herd:status"]["payload"],
|
|
594
|
+
o: RtClientOptions = {},
|
|
595
|
+
): Promise<RtResponse<Commands["herd:status"]["data"]>> {
|
|
596
|
+
return rtCommand<Commands["herd:status"]["data"]>("herd:status", { herd: a.herd }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
export function herdList(
|
|
600
|
+
a: Commands["herd:list"]["payload"] = {},
|
|
601
|
+
o: RtClientOptions = {},
|
|
602
|
+
): Promise<RtResponse<Commands["herd:list"]["data"]>> {
|
|
603
|
+
const payload: Record<string, unknown> = {};
|
|
604
|
+
if (a.all !== undefined) payload.all = a.all;
|
|
605
|
+
return rtCommand<Commands["herd:list"]["data"]>("herd:list", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
export function herdResume(
|
|
609
|
+
a: Commands["herd:resume"]["payload"],
|
|
610
|
+
o: RtClientOptions = {},
|
|
611
|
+
): Promise<RtResponse<Commands["herd:resume"]["data"]>> {
|
|
612
|
+
return rtCommand<Commands["herd:resume"]["data"]>("herd:resume", { herd: a.herd, session: a.session }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/** Closes a herdr pane, one CLI call under the runner's own 15s budget. */
|
|
616
|
+
export function herdClose(
|
|
617
|
+
a: Commands["herd:close"]["payload"],
|
|
618
|
+
o: RtClientOptions = {},
|
|
619
|
+
): Promise<RtResponse<Commands["herd:close"]["data"]>> {
|
|
620
|
+
return rtCommand<Commands["herd:close"]["data"]>("herd:close", { herd: a.herd, job: a.job }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30_000 });
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/** Three sequential herdr CLI calls (pane get, tab create, pane run), each under the runner's own 15s budget. */
|
|
624
|
+
export function herdAttend(
|
|
625
|
+
a: Commands["herd:attend"]["payload"],
|
|
626
|
+
o: RtClientOptions = {},
|
|
627
|
+
): Promise<RtResponse<Commands["herd:attend"]["data"]>> {
|
|
628
|
+
return rtCommand<Commands["herd:attend"]["data"]>("herd:attend", { herd: a.herd, job: a.job, callerWorkspace: a.callerWorkspace }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 60_000 });
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/** Closes panes, disposes worktrees and archives the room in one pass; the
|
|
632
|
+
worktree disposals alone can outrun a spawn's budget. */
|
|
633
|
+
export function herdWrapUp(
|
|
634
|
+
a: Commands["herd:wrap-up"]["payload"],
|
|
635
|
+
o: RtClientOptions = {},
|
|
636
|
+
): Promise<RtResponse<Commands["herd:wrap-up"]["data"]>> {
|
|
637
|
+
const payload: Record<string, unknown> = { herd: a.herd };
|
|
638
|
+
for (const k of ["closePanes", "dispose", "deleteJobDirs", "archiveRoom"] as const) if (a[k] !== undefined) payload[k] = a[k];
|
|
639
|
+
return rtCommand<Commands["herd:wrap-up"]["data"]>("herd:wrap-up", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 120_000 });
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/** Runs `herdr session stop`, one CLI call under the runner's own 15s budget. */
|
|
643
|
+
export function herdStopHidden(
|
|
644
|
+
_a: Commands["herd:stop-hidden"]["payload"],
|
|
645
|
+
o: RtClientOptions = {},
|
|
646
|
+
): Promise<RtResponse<Commands["herd:stop-hidden"]["data"]>> {
|
|
647
|
+
return rtCommand<Commands["herd:stop-hidden"]["data"]>("herd:stop-hidden", {}, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30_000 });
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// ─── Background server (daemon-owned background herdr session) ────────────
|
|
651
|
+
|
|
652
|
+
/** A cold ensure spawns `herdr server`, waits out bg-service's own 10s
|
|
653
|
+
readyTimeoutMs, then runs its parity probes sequentially -- the 15s
|
|
654
|
+
default budget every other rt-client call inherits can lose that race,
|
|
655
|
+
so this is the one wrapper that widens its own. */
|
|
656
|
+
export function bgEnsure(
|
|
657
|
+
a: Commands["bg:ensure"]["payload"] = {},
|
|
658
|
+
o: RtClientOptions = {},
|
|
659
|
+
): Promise<RtResponse<Commands["bg:ensure"]["data"]>> {
|
|
660
|
+
const payload: Record<string, unknown> = {};
|
|
661
|
+
if (a.claim !== undefined) payload.claim = a.claim;
|
|
662
|
+
return rtCommand<Commands["bg:ensure"]["data"]>("bg:ensure", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30_000 });
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/** Never ensures/spawns; a plain read of the current state. */
|
|
666
|
+
export function bgStatus(o: RtClientOptions = {}): Promise<RtResponse<Commands["bg:status"]["data"]>> {
|
|
667
|
+
return rtCommand<Commands["bg:status"]["data"]>("bg:status", {}, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/** Asks the daemon to stop the background server; refuses (ok:false) while any claim is live, naming the owners. */
|
|
671
|
+
export function bgStop(o: RtClientOptions = {}): Promise<RtResponse<Commands["bg:stop"]["data"]>> {
|
|
672
|
+
return rtCommand<Commands["bg:stop"]["data"]>("bg:stop", {}, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30_000 });
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
export function bgRelease(
|
|
676
|
+
a: Commands["bg:release"]["payload"],
|
|
677
|
+
o: RtClientOptions = {},
|
|
678
|
+
): Promise<RtResponse<Commands["bg:release"]["data"]>> {
|
|
679
|
+
return rtCommand<Commands["bg:release"]["data"]>("bg:release", { claim: a.claim }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
680
|
+
}
|