@h-rig/pi-rig 0.0.6-alpha.89 → 0.0.6-alpha.90

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.
@@ -45,13 +45,6 @@ function resolveGlobalConnectionsPath(env) {
45
45
  return resolve(stateDir, "connections.json");
46
46
  return resolve(homedir(), ".rig", "connections.json");
47
47
  }
48
- function inferRemoteProjectRoot(repoConnection, authState) {
49
- const repoSlug = cleanString(repoConnection?.project) ?? cleanString(authState?.selectedRepo);
50
- const checkoutBaseDir = cleanString(authState?.checkoutBaseDir);
51
- if (!repoSlug || !checkoutBaseDir || !repoSlug.includes("/"))
52
- return null;
53
- return resolve(checkoutBaseDir, repoSlug);
54
- }
55
48
  function discoverRigContext(env) {
56
49
  const cwd = cleanString(env.PWD);
57
50
  if (!cwd)
@@ -67,11 +60,11 @@ function discoverRigContext(env) {
67
60
  const server = readJson(resolve(projectRoot, ".rig", "state", "rig-server.json"));
68
61
  const host = cleanString(server?.host);
69
62
  const port = typeof server?.port === "number" ? server.port : null;
70
- const authToken2 = cleanString(server?.authToken);
63
+ const authToken = cleanString(server?.authToken);
71
64
  return {
72
65
  projectRoot,
73
66
  ...host && port ? { serverUrl: `http://${host}:${port}` } : {},
74
- ...authToken2 ? { authToken: authToken2 } : {}
67
+ ...authToken ? { authToken } : {}
75
68
  };
76
69
  }
77
70
  const global = readJson(resolveGlobalConnectionsPath(env));
@@ -79,22 +72,15 @@ function discoverRigContext(env) {
79
72
  const selectedConnection = connections[selected];
80
73
  const record = selectedConnection && typeof selectedConnection === "object" && !Array.isArray(selectedConnection) ? selectedConnection : null;
81
74
  const baseUrl = record?.kind === "remote" ? cleanString(record.baseUrl) : null;
82
- const authState = readJson(resolve(projectRoot, ".rig", "state", "github-auth.json"));
83
- const authToken = cleanString(authState?.apiSessionToken) ?? cleanString(authState?.sessionToken);
84
- const serverProjectRoot = cleanString(repoConnection?.serverProjectRoot) ?? inferRemoteProjectRoot(repoConnection, authState);
85
- return {
86
- projectRoot: serverProjectRoot ?? projectRoot,
87
- ...baseUrl ? { serverUrl: baseUrl } : {},
88
- ...authToken ? { authToken } : {}
89
- };
75
+ return { projectRoot, ...baseUrl ? { serverUrl: baseUrl } : {} };
90
76
  }
91
77
  function createRigContextFromEnv(env = process.env) {
92
78
  const runId = env.RIG_RUN_ID ?? env.RIG_SERVER_RUN_ID;
93
79
  const taskId = env.RIG_TASK_ID;
94
80
  const discovered = discoverRigContext(env);
95
- const serverUrl = cleanString(env.RIG_SERVER_URL) ?? cleanString(env.RIG_SERVER_BASE_URL) ?? discovered.serverUrl;
96
- const projectRoot = cleanString(env.RIG_PROJECT_ROOT) ?? cleanString(env.PROJECT_RIG_ROOT) ?? discovered.projectRoot;
97
- const authToken = cleanString(env.RIG_AUTH_TOKEN) ?? cleanString(env.RIG_SERVER_AUTH_TOKEN) ?? discovered.authToken;
81
+ const serverUrl = env.RIG_SERVER_URL ?? env.RIG_SERVER_BASE_URL ?? discovered.serverUrl;
82
+ const projectRoot = env.RIG_PROJECT_ROOT ?? env.PROJECT_RIG_ROOT ?? discovered.projectRoot;
83
+ const authToken = env.RIG_AUTH_TOKEN ?? env.RIG_SERVER_AUTH_TOKEN ?? discovered.authToken;
98
84
  const steeringPollMs = cleanNonNegativeInteger(env.RIG_STEERING_POLL_MS);
99
85
  const operatorSession = env.RIG_PI_OPERATOR_SESSION === "1" || env.RIG_PI_OPERATOR_SESSION === "true";
100
86
  const active = Boolean(runId || taskId || serverUrl || projectRoot);
@@ -135,16 +121,6 @@ function requireServerUrl(context) {
135
121
  }
136
122
  var BRIDGE_REQUEST_TIMEOUT_MS = 30000;
137
123
  var PROTOCOL_CHECK_TIMEOUT_MS = 1e4;
138
- function mergeCookie(existing, name, value) {
139
- const encoded = `${name}=${encodeURIComponent(value)}`;
140
- if (!existing?.trim())
141
- return encoded;
142
- const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
143
- return [...parts, encoded].join("; ");
144
- }
145
- function queryAuthFallbackEnabled(env = process.env) {
146
- return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
147
- }
148
124
 
149
125
  class RigBridgeClient {
150
126
  context;
@@ -155,22 +131,14 @@ class RigBridgeClient {
155
131
  }
156
132
  async request(pathname, init, timeoutMs = BRIDGE_REQUEST_TIMEOUT_MS) {
157
133
  const headers = new Headers(init?.headers);
158
- if (this.context.authToken) {
159
- const bearer = `Bearer ${this.context.authToken}`;
160
- if (!headers.has("authorization"))
161
- headers.set("authorization", bearer);
162
- if (!headers.has("x-auth"))
163
- headers.set("x-auth", bearer);
164
- headers.set("cookie", mergeCookie(headers.get("cookie"), "rig_auth", this.context.authToken));
134
+ if (this.context.authToken && !headers.has("authorization")) {
135
+ headers.set("authorization", `Bearer ${this.context.authToken}`);
165
136
  }
166
137
  if (this.context.projectRoot && !headers.has("x-rig-project-root")) {
167
138
  headers.set("x-rig-project-root", this.context.projectRoot);
168
139
  }
169
140
  const signal = init?.signal ?? (timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : undefined);
170
- const requestUrl = new URL(joinUrl(requireServerUrl(this.context), pathname));
171
- if (this.context.authToken && queryAuthFallbackEnabled())
172
- requestUrl.searchParams.set("rt", this.context.authToken);
173
- const response = await this.fetchImpl(requestUrl.toString(), { ...init, headers, signal });
141
+ const response = await this.fetchImpl(joinUrl(requireServerUrl(this.context), pathname), { ...init, headers, signal });
174
142
  return readJsonResponse(response);
175
143
  }
176
144
  async status(timeoutMs) {
@@ -53,17 +53,8 @@ function createRigSlashCommands(input) {
53
53
  notify("Usage: /rig steer <message>", "error");
54
54
  return;
55
55
  }
56
- const result = await input.client.steer(message);
57
- const accepted = result.ok !== false && result.queued !== false;
58
- if (!accepted) {
59
- const reason = typeof result.error === "string" && result.error.trim() ? `: ${result.error.trim()}` : "";
60
- notify(`Rig did not accept the steering message${reason}.`, "error");
61
- return;
62
- }
63
- const entry = result.message && typeof result.message === "object" && !Array.isArray(result.message) ? result.message : null;
64
- const runId = String(entry?.runId ?? second ?? input.context.runId ?? "run");
65
- const preview = message.length > 80 ? `${message.slice(0, 79)}\u2026` : message;
66
- notify(`Steering queued for ${runId} \u2192 "${preview}" \u2014 the worker applies it at its next checkpoint.`, "info");
56
+ await input.client.steer(message);
57
+ notify("Rig steering message queued.", "info");
67
58
  return;
68
59
  }
69
60
  if (first === "stop") {
@@ -46,11 +46,13 @@ export type PiRigBridgeGate = {
46
46
  readonly status: RigProtocolCheck["status"];
47
47
  };
48
48
  export type PiRigBridgeGateCheck = (ctx: unknown) => Promise<PiRigBridgeGate>;
49
+ /** Live refresh control handed to the operator widget by the WS bridge:
50
+ * while the socket is up, pushes (rig.event / snapshotInvalidated) drive the
51
+ * widget instead of the 1s status poll. */
49
52
  export type OperatorLiveRefresh = {
50
53
  isConnected(): boolean;
51
54
  /** Returns true (and resets) when a push arrived since the last check. */
52
55
  consumePushTrigger(): boolean;
53
- dispose(): void;
54
56
  };
55
57
  export default function createPiRigExtension(pi: MinimalPiApi, options?: {
56
58
  state?: PiRigExtensionState;
package/dist/src/index.js CHANGED
@@ -47,13 +47,6 @@ function resolveGlobalConnectionsPath(env) {
47
47
  return resolve(stateDir, "connections.json");
48
48
  return resolve(homedir(), ".rig", "connections.json");
49
49
  }
50
- function inferRemoteProjectRoot(repoConnection, authState) {
51
- const repoSlug = cleanString(repoConnection?.project) ?? cleanString(authState?.selectedRepo);
52
- const checkoutBaseDir = cleanString(authState?.checkoutBaseDir);
53
- if (!repoSlug || !checkoutBaseDir || !repoSlug.includes("/"))
54
- return null;
55
- return resolve(checkoutBaseDir, repoSlug);
56
- }
57
50
  function discoverRigContext(env) {
58
51
  const cwd = cleanString(env.PWD);
59
52
  if (!cwd)
@@ -69,11 +62,11 @@ function discoverRigContext(env) {
69
62
  const server = readJson(resolve(projectRoot, ".rig", "state", "rig-server.json"));
70
63
  const host = cleanString(server?.host);
71
64
  const port = typeof server?.port === "number" ? server.port : null;
72
- const authToken2 = cleanString(server?.authToken);
65
+ const authToken = cleanString(server?.authToken);
73
66
  return {
74
67
  projectRoot,
75
68
  ...host && port ? { serverUrl: `http://${host}:${port}` } : {},
76
- ...authToken2 ? { authToken: authToken2 } : {}
69
+ ...authToken ? { authToken } : {}
77
70
  };
78
71
  }
79
72
  const global = readJson(resolveGlobalConnectionsPath(env));
@@ -81,22 +74,15 @@ function discoverRigContext(env) {
81
74
  const selectedConnection = connections[selected];
82
75
  const record = selectedConnection && typeof selectedConnection === "object" && !Array.isArray(selectedConnection) ? selectedConnection : null;
83
76
  const baseUrl = record?.kind === "remote" ? cleanString(record.baseUrl) : null;
84
- const authState = readJson(resolve(projectRoot, ".rig", "state", "github-auth.json"));
85
- const authToken = cleanString(authState?.apiSessionToken) ?? cleanString(authState?.sessionToken);
86
- const serverProjectRoot = cleanString(repoConnection?.serverProjectRoot) ?? inferRemoteProjectRoot(repoConnection, authState);
87
- return {
88
- projectRoot: serverProjectRoot ?? projectRoot,
89
- ...baseUrl ? { serverUrl: baseUrl } : {},
90
- ...authToken ? { authToken } : {}
91
- };
77
+ return { projectRoot, ...baseUrl ? { serverUrl: baseUrl } : {} };
92
78
  }
93
79
  function createRigContextFromEnv(env = process.env) {
94
80
  const runId = env.RIG_RUN_ID ?? env.RIG_SERVER_RUN_ID;
95
81
  const taskId = env.RIG_TASK_ID;
96
82
  const discovered = discoverRigContext(env);
97
- const serverUrl = cleanString(env.RIG_SERVER_URL) ?? cleanString(env.RIG_SERVER_BASE_URL) ?? discovered.serverUrl;
98
- const projectRoot = cleanString(env.RIG_PROJECT_ROOT) ?? cleanString(env.PROJECT_RIG_ROOT) ?? discovered.projectRoot;
99
- const authToken = cleanString(env.RIG_AUTH_TOKEN) ?? cleanString(env.RIG_SERVER_AUTH_TOKEN) ?? discovered.authToken;
83
+ const serverUrl = env.RIG_SERVER_URL ?? env.RIG_SERVER_BASE_URL ?? discovered.serverUrl;
84
+ const projectRoot = env.RIG_PROJECT_ROOT ?? env.PROJECT_RIG_ROOT ?? discovered.projectRoot;
85
+ const authToken = env.RIG_AUTH_TOKEN ?? env.RIG_SERVER_AUTH_TOKEN ?? discovered.authToken;
100
86
  const steeringPollMs = cleanNonNegativeInteger(env.RIG_STEERING_POLL_MS);
101
87
  const operatorSession = env.RIG_PI_OPERATOR_SESSION === "1" || env.RIG_PI_OPERATOR_SESSION === "true";
102
88
  const active = Boolean(runId || taskId || serverUrl || projectRoot);
@@ -137,16 +123,6 @@ function requireServerUrl(context) {
137
123
  }
138
124
  var BRIDGE_REQUEST_TIMEOUT_MS = 30000;
139
125
  var PROTOCOL_CHECK_TIMEOUT_MS = 1e4;
140
- function mergeCookie(existing, name, value) {
141
- const encoded = `${name}=${encodeURIComponent(value)}`;
142
- if (!existing?.trim())
143
- return encoded;
144
- const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
145
- return [...parts, encoded].join("; ");
146
- }
147
- function queryAuthFallbackEnabled(env = process.env) {
148
- return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
149
- }
150
126
 
151
127
  class RigBridgeClient {
152
128
  context;
@@ -157,22 +133,14 @@ class RigBridgeClient {
157
133
  }
158
134
  async request(pathname, init, timeoutMs = BRIDGE_REQUEST_TIMEOUT_MS) {
159
135
  const headers = new Headers(init?.headers);
160
- if (this.context.authToken) {
161
- const bearer = `Bearer ${this.context.authToken}`;
162
- if (!headers.has("authorization"))
163
- headers.set("authorization", bearer);
164
- if (!headers.has("x-auth"))
165
- headers.set("x-auth", bearer);
166
- headers.set("cookie", mergeCookie(headers.get("cookie"), "rig_auth", this.context.authToken));
136
+ if (this.context.authToken && !headers.has("authorization")) {
137
+ headers.set("authorization", `Bearer ${this.context.authToken}`);
167
138
  }
168
139
  if (this.context.projectRoot && !headers.has("x-rig-project-root")) {
169
140
  headers.set("x-rig-project-root", this.context.projectRoot);
170
141
  }
171
142
  const signal = init?.signal ?? (timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : undefined);
172
- const requestUrl = new URL(joinUrl(requireServerUrl(this.context), pathname));
173
- if (this.context.authToken && queryAuthFallbackEnabled())
174
- requestUrl.searchParams.set("rt", this.context.authToken);
175
- const response = await this.fetchImpl(requestUrl.toString(), { ...init, headers, signal });
143
+ const response = await this.fetchImpl(joinUrl(requireServerUrl(this.context), pathname), { ...init, headers, signal });
176
144
  return readJsonResponse(response);
177
145
  }
178
146
  async status(timeoutMs) {
@@ -699,17 +667,8 @@ function createRigSlashCommands(input) {
699
667
  notify("Usage: /rig steer <message>", "error");
700
668
  return;
701
669
  }
702
- const result = await input.client.steer(message);
703
- const accepted = result.ok !== false && result.queued !== false;
704
- if (!accepted) {
705
- const reason = typeof result.error === "string" && result.error.trim() ? `: ${result.error.trim()}` : "";
706
- notify(`Rig did not accept the steering message${reason}.`, "error");
707
- return;
708
- }
709
- const entry = result.message && typeof result.message === "object" && !Array.isArray(result.message) ? result.message : null;
710
- const runId = String(entry?.runId ?? second ?? input.context.runId ?? "run");
711
- const preview = message.length > 80 ? `${message.slice(0, 79)}\u2026` : message;
712
- notify(`Steering queued for ${runId} \u2192 "${preview}" \u2014 the worker applies it at its next checkpoint.`, "info");
670
+ await input.client.steer(message);
671
+ notify("Rig steering message queued.", "info");
713
672
  return;
714
673
  }
715
674
  if (first === "stop") {
@@ -808,7 +767,7 @@ async function createLiveMirror(input) {
808
767
  renderTimer = setTimeout(() => {
809
768
  renderTimer = null;
810
769
  tui?.requestRender?.();
811
- }, 16);
770
+ }, 33);
812
771
  renderTimer.unref?.();
813
772
  };
814
773
  pi.registerMessageRenderer?.(DRONE_MESSAGE_TYPE, (message) => {
@@ -1027,52 +986,35 @@ function createPiRigExtensionState(input = {}) {
1027
986
  ...input.webSocketFactory ? { webSocketFactory: input.webSocketFactory } : {}
1028
987
  };
1029
988
  }
1030
- function isStalePiContextError(error) {
1031
- const message = error instanceof Error ? error.message : String(error);
1032
- return /ctx is stale|stale after session replacement|session replacement or reload/i.test(message);
1033
- }
1034
- function safeUiCall(action) {
1035
- try {
1036
- action();
1037
- } catch (error) {
1038
- if (!isStalePiContextError(error)) {
1039
- return;
1040
- }
1041
- }
1042
- }
1043
- function uiOf(ctx) {
1044
- try {
1045
- const ui = ctx && typeof ctx === "object" ? ctx.ui : null;
1046
- return ui && typeof ui === "object" ? ui : null;
1047
- } catch {
1048
- return null;
1049
- }
1050
- }
1051
989
  function notify(ctx, message, level = "info") {
1052
- const ui = uiOf(ctx);
1053
- const notifyFn = ui?.notify;
990
+ const ui = ctx && typeof ctx === "object" ? ctx.ui : null;
991
+ const notifyFn = ui && typeof ui === "object" ? ui.notify : null;
1054
992
  if (typeof notifyFn === "function") {
1055
- safeUiCall(() => notifyFn.call(ui, message, level));
993
+ notifyFn.call(ui, message, level);
1056
994
  }
1057
995
  }
1058
996
  function canNotify(ctx) {
1059
- const ui = uiOf(ctx);
1060
- return Boolean(ui && typeof ui.notify === "function");
997
+ const ui = ctx && typeof ctx === "object" ? ctx.ui : null;
998
+ return Boolean(ui && typeof ui === "object" && typeof ui.notify === "function");
1061
999
  }
1062
1000
  function setWidget(ctx, id, lines) {
1063
- const ui = uiOf(ctx);
1064
- const setWidgetFn = ui?.setWidget;
1001
+ const ui = ctx && typeof ctx === "object" ? ctx.ui : null;
1002
+ const setWidgetFn = ui && typeof ui === "object" ? ui.setWidget : null;
1065
1003
  if (typeof setWidgetFn === "function") {
1066
- safeUiCall(() => setWidgetFn.call(ui, id, lines));
1004
+ setWidgetFn.call(ui, id, lines);
1067
1005
  }
1068
1006
  }
1069
1007
  function setStatus(ctx, id, text) {
1070
- const ui = uiOf(ctx);
1071
- const setStatusFn = ui?.setStatus;
1008
+ const ui = ctx && typeof ctx === "object" ? ctx.ui : null;
1009
+ const setStatusFn = ui && typeof ui === "object" ? ui.setStatus : null;
1072
1010
  if (typeof setStatusFn === "function") {
1073
- safeUiCall(() => setStatusFn.call(ui, id, text));
1011
+ setStatusFn.call(ui, id, text);
1074
1012
  }
1075
1013
  }
1014
+ function uiOf(ctx) {
1015
+ const ui = ctx && typeof ctx === "object" ? ctx.ui : null;
1016
+ return ui && typeof ui === "object" ? ui : null;
1017
+ }
1076
1018
  function setTitle(ctx, title) {
1077
1019
  const ui = uiOf(ctx);
1078
1020
  const setTitleFn = ui?.setTitle;
@@ -1239,31 +1181,24 @@ function startOperatorRunStatusLine(state, ctx, live) {
1239
1181
  return;
1240
1182
  const shortId = state.runId.slice(0, 8);
1241
1183
  let inFlight = false;
1242
- let disposed = false;
1243
1184
  let lastRefreshAt = 0;
1244
1185
  const refresh = async () => {
1245
- if (disposed || inFlight)
1186
+ if (inFlight)
1246
1187
  return;
1247
1188
  inFlight = true;
1248
1189
  lastRefreshAt = Date.now();
1249
1190
  try {
1250
1191
  const run = runPayload(await state.client.attach(state.runId));
1251
- if (disposed)
1252
- return;
1253
1192
  const status = String(run.status ?? "unknown");
1254
1193
  setStatus(ctx, "rig", `drone ${shortId} \xB7 ${status} \xB7 ${shortPath(runLocation(run))}`);
1255
1194
  } catch (error) {
1256
- if (!disposed) {
1257
- setStatus(ctx, "rig", `drone ${shortId} \xB7 server unreachable: ${error instanceof Error ? error.message : String(error)}`);
1258
- }
1195
+ setStatus(ctx, "rig", `drone ${shortId} \xB7 server unreachable: ${error instanceof Error ? error.message : String(error)}`);
1259
1196
  } finally {
1260
1197
  inFlight = false;
1261
1198
  }
1262
1199
  };
1263
1200
  refresh();
1264
1201
  const timer = setInterval(() => {
1265
- if (disposed)
1266
- return;
1267
1202
  const triggered = live?.consumePushTrigger() ?? false;
1268
1203
  if ((live?.isConnected() ?? false) && !triggered && Date.now() - lastRefreshAt < OPERATOR_WIDGET_WS_FALLBACK_MS) {
1269
1204
  return;
@@ -1271,10 +1206,6 @@ function startOperatorRunStatusLine(state, ctx, live) {
1271
1206
  refresh();
1272
1207
  }, 5000);
1273
1208
  unrefTimer(timer);
1274
- return () => {
1275
- disposed = true;
1276
- clearInterval(timer);
1277
- };
1278
1209
  }
1279
1210
  function operatorInboxNotification(event) {
1280
1211
  const type = typeof event.type === "string" ? event.type : null;
@@ -1327,8 +1258,7 @@ function startOperatorBridge(state, ctx) {
1327
1258
  const triggered = pushTrigger;
1328
1259
  pushTrigger = false;
1329
1260
  return triggered;
1330
- },
1331
- dispose: () => socket.close()
1261
+ }
1332
1262
  };
1333
1263
  }
1334
1264
  function workerStatusLine(status) {
@@ -1450,30 +1380,24 @@ function registerOperatorConsoleCommands(pi, state, tryRegister) {
1450
1380
  function startWorkerSessionMirror(pi, state, ctx) {
1451
1381
  if (!state.operatorSession || !state.active || !state.runId)
1452
1382
  return;
1453
- let disposed = false;
1454
1383
  let mirror = null;
1455
1384
  const pendingEvents = [];
1456
1385
  createLiveMirror({ pi }).then((created) => {
1457
- if (disposed)
1458
- return;
1459
1386
  mirror = created;
1460
1387
  const ui = uiOf(ctx);
1461
1388
  if (ui && typeof ui.setWidget === "function") {
1462
- safeUiCall(() => ui.setWidget("rig-tui-capture", (tui) => created.captureTui(tui)));
1389
+ ui.setWidget("rig-tui-capture", (tui) => created.captureTui(tui));
1463
1390
  }
1464
1391
  for (const event of pendingEvents.splice(0))
1465
1392
  created.handleWorkerEvent(event);
1466
1393
  }).catch((error) => {
1467
- if (!disposed)
1468
- notify(ctx, `Live drone transcript unavailable: ${error instanceof Error ? error.message : String(error)}`, "error");
1394
+ notify(ctx, `Live drone transcript unavailable: ${error instanceof Error ? error.message : String(error)}`, "error");
1469
1395
  });
1470
1396
  const socket = new RigWorkerEventsSocket({
1471
1397
  context: state,
1472
1398
  webSocketFactory: state.webSocketFactory,
1473
1399
  handlers: {
1474
1400
  onFrame: (frame) => {
1475
- if (disposed)
1476
- return;
1477
1401
  if (frame.type === "status.update") {
1478
1402
  const status = frame.status && typeof frame.status === "object" && !Array.isArray(frame.status) ? frame.status : null;
1479
1403
  if (status)
@@ -1495,21 +1419,14 @@ function startWorkerSessionMirror(pi, state, ctx) {
1495
1419
  pendingEvents.push(event);
1496
1420
  },
1497
1421
  onConnect: () => {
1498
- if (!disposed)
1499
- setStatus(ctx, "rig-worker", "drone link live");
1422
+ setStatus(ctx, "rig-worker", "drone link live");
1500
1423
  },
1501
1424
  onDisconnect: () => {
1502
- if (!disposed)
1503
- setStatus(ctx, "rig-worker", "drone link down (reconnecting\u2026)");
1425
+ setStatus(ctx, "rig-worker", "drone link down (reconnecting\u2026)");
1504
1426
  }
1505
1427
  }
1506
1428
  });
1507
1429
  socket.start();
1508
- return () => {
1509
- disposed = true;
1510
- pendingEvents.splice(0);
1511
- socket.close();
1512
- };
1513
1430
  }
1514
1431
  async function forwardWorkerUiRequest(state, ctx, event) {
1515
1432
  const request = event.request && typeof event.request === "object" && !Array.isArray(event.request) ? event.request : event;
@@ -1579,10 +1496,8 @@ function startWorkerCommandRegistration(pi, state, ctx) {
1579
1496
  const registeredNames = new Set;
1580
1497
  let attempts = 0;
1581
1498
  let inFlight = false;
1582
- let disposed = false;
1583
- let timer = null;
1584
1499
  const attempt = async () => {
1585
- if (disposed || inFlight)
1500
+ if (inFlight)
1586
1501
  return false;
1587
1502
  inFlight = true;
1588
1503
  attempts += 1;
@@ -1593,84 +1508,64 @@ function startWorkerCommandRegistration(pi, state, ctx) {
1593
1508
  }
1594
1509
  };
1595
1510
  attempt().then((ready) => {
1596
- if (disposed || ready)
1511
+ if (ready)
1597
1512
  return;
1598
- timer = setInterval(() => {
1599
- if (disposed || attempts >= 60) {
1600
- if (timer)
1601
- clearInterval(timer);
1602
- timer = null;
1513
+ const timer = setInterval(() => {
1514
+ if (attempts >= 60) {
1515
+ clearInterval(timer);
1603
1516
  return;
1604
1517
  }
1605
1518
  attempt().then((nextReady) => {
1606
- if (nextReady && timer) {
1519
+ if (nextReady)
1607
1520
  clearInterval(timer);
1608
- timer = null;
1609
- }
1610
1521
  });
1611
1522
  }, 2000);
1612
1523
  unrefTimer(timer);
1613
1524
  });
1614
- return () => {
1615
- disposed = true;
1616
- if (timer)
1617
- clearInterval(timer);
1618
- timer = null;
1619
- };
1620
1525
  }
1621
1526
  function startSteeringBridge(pi, state, ctx, gate, deliveredIds) {
1622
1527
  if (state.operatorSession || !state.active || !state.runId || typeof pi.sendUserMessage !== "function")
1623
1528
  return;
1624
1529
  const runId = state.runId;
1625
- let disposed = false;
1626
1530
  const socket = new RigBridgeSocket({
1627
1531
  context: state,
1628
1532
  webSocketFactory: state.webSocketFactory,
1629
1533
  handlers: {
1630
1534
  onSteeringMessage: (message) => {
1631
- if (disposed)
1632
- return;
1633
1535
  (async () => {
1634
1536
  try {
1635
- if (disposed || !await deliverSteeringMessage(pi, deliveredIds, message))
1537
+ if (!await deliverSteeringMessage(pi, deliveredIds, message))
1636
1538
  return;
1637
1539
  const id = typeof message.id === "string" && message.id.trim() ? message.id : null;
1638
1540
  if (id)
1639
1541
  socket.ackSteering(runId, [id]);
1640
1542
  notify(ctx, "Delivered 1 Rig steering message.");
1641
1543
  } catch (error) {
1642
- if (!disposed)
1643
- notify(ctx, `Rig steering sync failed: ${error instanceof Error ? error.message : String(error)}`, "error");
1544
+ notify(ctx, `Rig steering sync failed: ${error instanceof Error ? error.message : String(error)}`, "error");
1644
1545
  }
1645
1546
  })();
1646
1547
  },
1647
1548
  onConnect: () => {
1648
- if (disposed)
1649
- return;
1650
1549
  consumeQueuedSteering(pi, state, ctx, gate, deliveredIds);
1651
1550
  }
1652
1551
  }
1653
1552
  });
1654
1553
  (async () => {
1655
1554
  const gateResult = await gate(ctx);
1656
- if (disposed || !gateResult.allowed)
1555
+ if (!gateResult.allowed)
1657
1556
  return;
1658
1557
  if (gateResult.status === "compatible") {
1659
1558
  socket.start();
1660
1559
  }
1661
1560
  })();
1662
1561
  const intervalMs = state.steeringPollMs ?? 1000;
1663
- if (intervalMs <= 0) {
1664
- return () => {
1665
- disposed = true;
1666
- socket.close();
1667
- };
1668
- }
1562
+ if (intervalMs <= 0)
1563
+ return;
1669
1564
  const WS_CONNECTED_SWEEP_MS = 1e4;
1670
1565
  let inFlight = false;
1671
1566
  let lastSweepAt = 0;
1672
1567
  const timer = setInterval(() => {
1673
- if (disposed || inFlight)
1568
+ if (inFlight)
1674
1569
  return;
1675
1570
  if (socket.connected && Date.now() - lastSweepAt < WS_CONNECTED_SWEEP_MS)
1676
1571
  return;
@@ -1681,35 +1576,11 @@ function startSteeringBridge(pi, state, ctx, gate, deliveredIds) {
1681
1576
  });
1682
1577
  }, intervalMs);
1683
1578
  unrefTimer(timer);
1684
- return () => {
1685
- disposed = true;
1686
- clearInterval(timer);
1687
- socket.close();
1688
- };
1689
1579
  }
1690
1580
  function createPiRigExtension(pi, options = {}) {
1691
1581
  const state = options.state ?? createPiRigExtensionState();
1692
1582
  const gate = createBridgeGate(state);
1693
1583
  const deliveredSteeringIds = new Set;
1694
- const sessionDisposables = new Set;
1695
- const runtimeDisposables = new Set;
1696
- const addDisposable = (target, disposable) => {
1697
- if (disposable)
1698
- target.add(disposable);
1699
- };
1700
- const disposeSet = (target) => {
1701
- for (const dispose of target) {
1702
- try {
1703
- dispose();
1704
- } catch {}
1705
- }
1706
- target.clear();
1707
- };
1708
- const disposeSessionResources = () => disposeSet(sessionDisposables);
1709
- const disposeRuntimeResources = () => {
1710
- disposeSet(sessionDisposables);
1711
- disposeSet(runtimeDisposables);
1712
- };
1713
1584
  const commands = createRigSlashCommands({
1714
1585
  context: state,
1715
1586
  client: state.client,
@@ -1759,14 +1630,10 @@ function createPiRigExtension(pi, options = {}) {
1759
1630
  }
1760
1631
  }));
1761
1632
  }
1762
- addDisposable(runtimeDisposables, startSteeringBridge(pi, state, globalThis, gate, deliveredSteeringIds));
1633
+ startSteeringBridge(pi, state, globalThis, gate, deliveredSteeringIds);
1763
1634
  }
1764
1635
  pi.on?.("input", async (event, ctx) => handleOperatorInput(event, state, ctx, gate));
1765
- pi.on?.("session_shutdown", () => {
1766
- disposeRuntimeResources();
1767
- });
1768
1636
  pi.on?.("session_start", async (_event, ctx) => {
1769
- disposeSessionResources();
1770
1637
  if (!state.active || !state.runId)
1771
1638
  return;
1772
1639
  const shortId = state.runId.slice(0, 8);
@@ -1782,12 +1649,11 @@ function createPiRigExtension(pi, options = {}) {
1782
1649
  return;
1783
1650
  }
1784
1651
  setStatus(ctx, "rig", `drone ${shortId} \xB7 connecting\u2026`);
1785
- const live = gateResult.allowed ? startOperatorBridge(state, ctx) : undefined;
1786
- addDisposable(sessionDisposables, live?.dispose);
1787
- addDisposable(sessionDisposables, startOperatorRunStatusLine(state, ctx, live));
1788
- if (state.operatorSession && gateResult.allowed) {
1789
- addDisposable(sessionDisposables, startWorkerSessionMirror(pi, state, ctx));
1790
- addDisposable(sessionDisposables, startWorkerCommandRegistration(pi, state, ctx));
1652
+ const live = gateResult.status === "compatible" ? startOperatorBridge(state, ctx) : undefined;
1653
+ startOperatorRunStatusLine(state, ctx, live);
1654
+ if (state.operatorSession && gateResult.status === "compatible") {
1655
+ startWorkerSessionMirror(pi, state, ctx);
1656
+ startWorkerCommandRegistration(pi, state, ctx);
1791
1657
  }
1792
1658
  await consumeQueuedSteering(pi, state, ctx, gate, deliveredSteeringIds);
1793
1659
  });
@@ -64,7 +64,7 @@ async function createLiveMirror(input) {
64
64
  renderTimer = setTimeout(() => {
65
65
  renderTimer = null;
66
66
  tui?.requestRender?.();
67
- }, 16);
67
+ }, 33);
68
68
  renderTimer.unref?.();
69
69
  };
70
70
  pi.registerMessageRenderer?.(DRONE_MESSAGE_TYPE, (message) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@h-rig/pi-rig",
3
- "version": "0.0.6-alpha.89",
3
+ "version": "0.0.6-alpha.90",
4
4
  "type": "module",
5
5
  "description": "Rig package",
6
6
  "license": "UNLICENSED",
@@ -38,7 +38,7 @@
38
38
  ]
39
39
  },
40
40
  "dependencies": {
41
- "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.89"
41
+ "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.90"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "@earendil-works/pi-coding-agent": ">=0.79.0",