okstra 0.163.1 → 0.163.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.163.1",
3
+ "version": "0.163.2",
4
4
  "description": "Host-aware multi-provider cross-verification orchestrator runtime and agent skills.",
5
5
  "license": "MIT",
6
6
  "author": "devonshin",
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.163.1",
3
- "builtAt": "2026-08-09T08:20:00.790Z",
2
+ "package": "0.163.2",
3
+ "builtAt": "2026-08-09T12:57:56.931Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -14,7 +14,7 @@ import socket
14
14
  import subprocess
15
15
  from dataclasses import dataclass
16
16
  from pathlib import Path
17
- from typing import Any, Sequence
17
+ from typing import Any, Collection, Sequence
18
18
 
19
19
  PING_OK = "PONG"
20
20
 
@@ -165,15 +165,31 @@ class Placement:
165
165
 
166
166
 
167
167
  def plan_worker_placement(
168
- panes: Sequence[PaneGeometry], *, lead_pane_id: str, min_columns: int
168
+ panes: Sequence[PaneGeometry],
169
+ *,
170
+ lead_pane_id: str,
171
+ owned_surface_ids: Collection[str],
172
+ min_columns: int,
169
173
  ) -> Placement:
170
174
  """Pick the next worker slot from the workspace's current geometry.
171
175
 
172
176
  Stateless by design: okstra records surface UUIDs, never a layout, so a
173
177
  resumed or crashed run cannot carry a layout model that no longer matches
174
178
  the screen. Every dispatch re-reads the panes and derives the next slot.
179
+
180
+ Those same UUIDs say which panes okstra may place into. A workspace also
181
+ holds panes okstra never opened — another agent session, a shell the user
182
+ keeps around — and "not the lead" does not make a pane a worker slot. Taken
183
+ as one, a stranger's pane is split or, when it is too narrow to split,
184
+ stacked into: the workers land as background tabs in someone else's window,
185
+ so nothing appears on screen and that window grows tabs it did not ask for.
175
186
  """
176
- workers = [pane for pane in panes if pane.pane_id != lead_pane_id]
187
+ workers = [
188
+ pane
189
+ for pane in panes
190
+ if pane.pane_id != lead_pane_id
191
+ and _holds_an_okstra_surface(pane, owned_surface_ids)
192
+ ]
177
193
  if not workers:
178
194
  return Placement(pane_id=lead_pane_id, direction="right", stack_as_tab=False)
179
195
 
@@ -196,6 +212,17 @@ def lead_shrink_points(lead: PaneGeometry, *, target_columns: int) -> int:
196
212
  return surplus * lead.cell_width_points
197
213
 
198
214
 
215
+ def _holds_an_okstra_surface(
216
+ pane: PaneGeometry, owned_surface_ids: Collection[str]
217
+ ) -> bool:
218
+ """Whether okstra opened anything in this pane.
219
+
220
+ Any one recorded surface is enough: workers that stacked as tabs share a
221
+ pane, and only the tab that opened it carries the UUID okstra split on.
222
+ """
223
+ return any(surface_id in owned_surface_ids for surface_id in pane.surface_ids)
224
+
225
+
199
226
  def _widen_the_grid(
200
227
  workers: Sequence[PaneGeometry], *, min_columns: int
201
228
  ) -> Placement:
@@ -270,18 +297,30 @@ def list_panes(workspace: str) -> list[PaneGeometry]:
270
297
 
271
298
 
272
299
  def spawn_worker_surface(
273
- *, workspace: str, cwd: Path, command: Sequence[str], title: str
300
+ *,
301
+ workspace: str,
302
+ cwd: Path,
303
+ command: Sequence[str],
304
+ title: str,
305
+ owned_surface_ids: Collection[str],
274
306
  ) -> str:
275
307
  """Start one worker beside the lead and return its surface UUID.
276
308
 
277
309
  The UUID is what okstra records and later closes by. Positional refs cannot
278
310
  serve that purpose: cmux renumbers them as surfaces open and close, so a
279
311
  close by ref can land on a pane okstra never created.
312
+
313
+ `owned_surface_ids` are the UUIDs earlier dispatches returned. They are what
314
+ keeps this placement inside okstra's own panes; the workspace belongs to the
315
+ user and may hold anything.
280
316
  """
281
317
  panes = list_panes(workspace)
282
318
  lead = _lead_pane(panes)
283
319
  placement = plan_worker_placement(
284
- panes, lead_pane_id=lead.pane_id, min_columns=WORKER_MIN_COLUMNS
320
+ panes,
321
+ lead_pane_id=lead.pane_id,
322
+ owned_surface_ids=owned_surface_ids,
323
+ min_columns=WORKER_MIN_COLUMNS,
285
324
  )
286
325
  target = _pane_by_id(panes, placement.pane_id)
287
326
  surface_uuid = _open_worker_surface(workspace, placement, target)
@@ -452,6 +452,7 @@ def _start_cmux_or_degrade(plan: DispatchPlan, job: WorkerJob) -> WorkerHandle:
452
452
  cwd=plan.project_root,
453
453
  command=job.command,
454
454
  title=f"{job.worker_id}-worker",
455
+ owned_surface_ids=_opened_cmux_surfaces(plan.team_state_path),
455
456
  )
456
457
  except (RuntimeError, OSError, subprocess.SubprocessError):
457
458
  return _run_cli_wrapper(plan, job, BACKEND_CMUX_PANE)
@@ -460,6 +461,28 @@ def _start_cmux_or_degrade(plan: DispatchPlan, job: WorkerJob) -> WorkerHandle:
460
461
  )
461
462
 
462
463
 
464
+ def _opened_cmux_surfaces(team_state_path: Path) -> tuple[str, ...]:
465
+ """The surfaces this run has already opened in the user's cmux workspace.
466
+
467
+ Placement needs them to tell okstra's own panes from the rest of the
468
+ workspace, which belongs to the user and may hold another agent session or
469
+ a shell okstra must not split or stack into. A degraded dispatch opened no
470
+ surface and records an empty id, which would name no pane at all.
471
+ """
472
+ payload = _load_json_object(team_state_path, "team-state")
473
+ dispatches = payload.get("workerDispatches")
474
+ if not isinstance(dispatches, list):
475
+ return ()
476
+ opened = []
477
+ for record in dispatches:
478
+ if not isinstance(record, dict):
479
+ continue
480
+ surface_id = str(record.get("paneId", ""))
481
+ if surface_id:
482
+ opened.append(surface_id)
483
+ return tuple(opened)
484
+
485
+
463
486
  def _start_tmux_or_degrade(plan: DispatchPlan, job: WorkerJob) -> WorkerHandle:
464
487
  lead_pane = tmux.resolve_caller_pane()
465
488
  if not lead_pane: