agent-relay-orchestrator 0.27.2 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay-orchestrator",
3
- "version": "0.27.2",
3
+ "version": "0.28.0",
4
4
  "description": "Agent Relay orchestrator — manages agent lifecycle across hosts",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,7 +16,7 @@
16
16
  "test": "bun test"
17
17
  },
18
18
  "dependencies": {
19
- "agent-relay-sdk": "0.2.16"
19
+ "agent-relay-sdk": "0.2.17"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/bun": "latest",
@@ -144,6 +144,19 @@ export function decodeControlOutput(data: string): Uint8Array {
144
144
  return Uint8Array.from(out);
145
145
  }
146
146
 
147
+ // The control stream is read as latin1 so the `%output` octal path above sees faithful
148
+ // bytes. But command-reply blocks (capture-pane grid rows) arrive as raw UTF-8, so their
149
+ // lines reach us as the byte-faithful latin1 representation. Re-decode them to real UTF-8
150
+ // here, or multi-byte glyphs (box-drawing, powerline) double-encode on the next repaint
151
+ // and render as mojibake (#270). Safe to do per line: no UTF-8 continuation byte is
152
+ // 0x0A/0x0D, so the newline split never lands mid-sequence.
153
+ const REPLY_UTF8_DECODER = new TextDecoder("utf-8");
154
+ export function latin1LineToUtf8(line: string): string {
155
+ const bytes = new Uint8Array(line.length);
156
+ for (let i = 0; i < line.length; i++) bytes[i] = line.charCodeAt(i) & 0xff;
157
+ return REPLY_UTF8_DECODER.decode(bytes);
158
+ }
159
+
147
160
  export type ControlLine =
148
161
  | { type: "output"; pane: string; bytes: Uint8Array }
149
162
  | { type: "exit"; reason?: string }
@@ -428,7 +441,7 @@ class SessionStream {
428
441
  if (!entry) return; // unexpected extra block — drop it rather than mis-correlate
429
442
  if (entry.timer) clearTimeout(entry.timer);
430
443
  if (isError) entry.reject(new Error(block.lines.join(" ").trim() || "tmux command error"));
431
- else entry.resolve(block.lines);
444
+ else entry.resolve(block.lines.map(latin1LineToUtf8));
432
445
  }
433
446
 
434
447
  private enqueue(bytes: Uint8Array): void {
@@ -1086,7 +1086,9 @@ function workspaceId(input: WorkspaceResolutionInput): string {
1086
1086
 
1087
1087
  function branchName(input: WorkspaceResolutionInput, id: string): string {
1088
1088
  const owner = input.policyName || input.label || input.automationId || "manual";
1089
- return `agent/${safeSegment(owner, 48)}/${safeSegment(id.replace(/^sp[_-]?/, ""), 24)}`;
1089
+ // 40 fits a full UUID (36) plus the `sp_`-stripped slack. A tighter cap (was 24)
1090
+ // sliced the session UUID mid-string, leaving an unaddressable, dangling ref (#282).
1091
+ return `agent/${safeSegment(owner, 48)}/${safeSegment(id.replace(/^sp[_-]?/, ""), 40)}`;
1090
1092
  }
1091
1093
 
1092
1094
  /** Next free `<branch>-N` cycle name for a recycled worktree (#206). Strips any