@heyhuynhgiabuu/pi-task 0.1.2 → 0.1.4

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/CHANGELOG.md CHANGED
@@ -4,6 +4,51 @@ All notable changes to `@heyhuynhgiabuu/pi-task` are documented here.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [0.1.4] — 2026-06-21
8
+
9
+ ### Fixed
10
+
11
+ - Detect the current tmux pane size before launching a task pane and choose
12
+ the split direction based on available space: side-by-side for wide panes,
13
+ stacked for narrow panes.
14
+ - Target the exact pane that was measured when running `tmux split-window`,
15
+ avoiding focus races where a different pane could be split.
16
+ - Apply the same pane-size-aware split logic to the subagent tmux helper.
17
+
18
+ ### Verified
19
+
20
+ - `npm test` passes
21
+ - `npm run typecheck` passes
22
+ - `npm run build` passes
23
+ - `npm run smoke` passes
24
+ - `npm pack --dry-run` succeeds
25
+ - Real tmux integration check passed for narrow `120x40` and wide `200x40`
26
+ sessions.
27
+
28
+ [0.1.4]: https://github.com/heyhuynhgiabuu/pi-task/releases/tag/v0.1.4
29
+
30
+ ## [0.1.3] — 2026-06-21
31
+
32
+ ### Fixed
33
+
34
+ - Replaced tmux task startup via `send-keys` with direct
35
+ `split-window <command>` execution so long, quoted `pi ...` launch
36
+ commands are not truncated or interrupted by terminal input buffering.
37
+ - Hardened tmux steering/follow-up text injection by using tmux buffers
38
+ (`load-buffer` + `paste-buffer`) instead of typing long text via
39
+ `send-keys`.
40
+
41
+ ### Verified
42
+
43
+ - `npm test` passes
44
+ - `npm run typecheck` passes
45
+ - `npm run build` passes
46
+ - `npm run smoke` passes
47
+ - Long task-tool tmux launch stress test passed with quotes, backticks,
48
+ shell expansions, redirects, newlines, unicode, and long prompt text.
49
+
50
+ [0.1.3]: https://github.com/heyhuynhgiabuu/pi-task/releases/tag/v0.1.3
51
+
7
52
  ## [0.1.2] — 2025
8
53
 
9
54
  ### Fixed
package/dist/helpers.d.ts CHANGED
@@ -50,7 +50,9 @@ export declare function parseResultXml(raw: string): ParsedResult;
50
50
  export declare function formatMs(ms: number): string;
51
51
  export declare function parseIdTimestamp(id: string): number;
52
52
  export declare function shellQuote(value: string): string;
53
- export declare function buildTmuxSendKeysArgs(paneId: string, command: string): string[];
53
+ export type TmuxSplitDirection = "-h" | "-v";
54
+ export declare function chooseTmuxSplitDirection(paneWidth: number, paneHeight: number): TmuxSplitDirection;
55
+ export declare function buildTmuxSplitWindowArgs(cwd: string, command: string, direction?: TmuxSplitDirection, targetPane?: string | null): string[];
54
56
  export interface BackgroundReceiptInput {
55
57
  taskId: string;
56
58
  agentType: string;
package/dist/helpers.js CHANGED
@@ -128,8 +128,29 @@ export function parseIdTimestamp(id) {
128
128
  export function shellQuote(value) {
129
129
  return `'${value.replace(/'/g, `'"'"'`)}'`;
130
130
  }
131
- export function buildTmuxSendKeysArgs(paneId, command) {
132
- return ["send-keys", "-t", paneId, command, "Enter"];
131
+ export function chooseTmuxSplitDirection(paneWidth, paneHeight) {
132
+ const minSideBySideWidth = 160;
133
+ const minStackedHeight = 24;
134
+ if (Number.isFinite(paneWidth) && paneWidth >= minSideBySideWidth) {
135
+ return "-h";
136
+ }
137
+ if (Number.isFinite(paneHeight) && paneHeight >= minStackedHeight) {
138
+ return "-v";
139
+ }
140
+ return "-h";
141
+ }
142
+ export function buildTmuxSplitWindowArgs(cwd, command, direction = "-h", targetPane) {
143
+ const args = [
144
+ "split-window",
145
+ direction,
146
+ "-P",
147
+ "-F",
148
+ "#{pane_id}",
149
+ ];
150
+ if (targetPane)
151
+ args.push("-t", targetPane);
152
+ args.push("-c", cwd, command);
153
+ return args;
133
154
  }
134
155
  export function formatBackgroundReceipt(input) {
135
156
  return [
package/dist/index.js CHANGED
@@ -22,7 +22,7 @@ import { dirname, join } from "node:path";
22
22
  import { fileURLToPath } from "node:url";
23
23
  import { Type } from "@sinclair/typebox";
24
24
  import { Text, truncateToWidth } from "@earendil-works/pi-tui";
25
- import { TASK_BACKGROUND_DEFAULT, TASK_RESULT_XML_INSTRUCTIONS, TASK_TOOL_DESCRIPTION, buildTmuxSendKeysArgs, formatBackgroundReceipt, buildPiArgs, parseResultXml, formatMs, shellQuote, discoverAgents, formatAgentList, countToolUses, readRecentToolCalls, } from "./helpers.js";
25
+ import { TASK_BACKGROUND_DEFAULT, TASK_RESULT_XML_INSTRUCTIONS, TASK_TOOL_DESCRIPTION, buildTmuxSplitWindowArgs, chooseTmuxSplitDirection, formatBackgroundReceipt, buildPiArgs, parseResultXml, formatMs, shellQuote, discoverAgents, formatAgentList, countToolUses, readRecentToolCalls, } from "./helpers.js";
26
26
  import { runSdkSubagent } from "./subagent/runSdk.js";
27
27
  import { checkTaskCompletion, waitForTaskCompletion as waitForSessionTaskCompletion, } from "./subagent/waitCompletion.js";
28
28
  import { buildAgentToolSelection } from "./agent-tools.js";
@@ -79,20 +79,28 @@ function getCurrentPaneId() {
79
79
  return null;
80
80
  }
81
81
  }
82
+ function getCurrentPaneSize(targetPane) {
83
+ try {
84
+ const args = ["display-message", "-p", "#{pane_width} #{pane_height}"];
85
+ if (targetPane)
86
+ args.splice(1, 0, "-t", targetPane);
87
+ const raw = tmuxCmd(args);
88
+ const [widthRaw, heightRaw] = raw.trim().split(/\s+/, 2);
89
+ const width = Number(widthRaw);
90
+ const height = Number(heightRaw);
91
+ if (!Number.isFinite(width) || !Number.isFinite(height))
92
+ return null;
93
+ return { width, height };
94
+ }
95
+ catch {
96
+ return null;
97
+ }
98
+ }
82
99
  function splitWindowPane(cwd, command) {
83
100
  const originalPane = getCurrentPaneId();
84
- const paneId = tmuxCmd([
85
- "split-window",
86
- "-h",
87
- "-P",
88
- "-F",
89
- "#{pane_id}",
90
- "-c",
91
- cwd,
92
- ]);
93
- execFileSync("tmux", buildTmuxSendKeysArgs(paneId, command), {
94
- stdio: "ignore",
95
- });
101
+ const paneSize = getCurrentPaneSize(originalPane);
102
+ const direction = chooseTmuxSplitDirection(paneSize?.width ?? 0, paneSize?.height ?? 0);
103
+ const paneId = tmuxCmd(buildTmuxSplitWindowArgs(cwd, command, direction, originalPane));
96
104
  return { paneId, originalPane };
97
105
  }
98
106
  function killAgentPane(paneId, originalPane) {
@@ -5,10 +5,14 @@ export declare function tmuxCmd(args: string[]): string;
5
5
  export declare function hasTmux(): boolean;
6
6
  export declare function paneExists(paneId: string): boolean;
7
7
  export declare function getCurrentPaneId(): string | null;
8
+ export declare function getCurrentPaneSize(targetPane?: string | null): {
9
+ width: number;
10
+ height: number;
11
+ } | null;
8
12
  export declare function splitWindowPane(cwd: string, command: string): {
9
13
  paneId: string;
10
14
  originalPane: string | null;
11
15
  };
12
16
  export declare function killAgentPane(paneId: string, originalPane: string | null): void;
13
- /** Inject keys into a running subagent pane (steer / follow-up). */
17
+ /** Inject text into a running subagent pane (steer / follow-up). */
14
18
  export declare function tmuxSteerPane(paneId: string, message: string): void;
@@ -2,6 +2,7 @@
2
2
  * Tmux helpers for subagent panes (shared by task extension).
3
3
  */
4
4
  import { execFileSync } from "node:child_process";
5
+ import { buildTmuxSplitWindowArgs, chooseTmuxSplitDirection } from "../helpers.js";
5
6
  export function tmuxCmd(args) {
6
7
  return execFileSync("tmux", args, {
7
8
  encoding: "utf-8",
@@ -34,18 +35,28 @@ export function getCurrentPaneId() {
34
35
  return null;
35
36
  }
36
37
  }
38
+ export function getCurrentPaneSize(targetPane) {
39
+ try {
40
+ const args = ["display-message", "-p", "#{pane_width} #{pane_height}"];
41
+ if (targetPane)
42
+ args.splice(1, 0, "-t", targetPane);
43
+ const raw = tmuxCmd(args);
44
+ const [widthRaw, heightRaw] = raw.trim().split(/\s+/, 2);
45
+ const width = Number(widthRaw);
46
+ const height = Number(heightRaw);
47
+ if (!Number.isFinite(width) || !Number.isFinite(height))
48
+ return null;
49
+ return { width, height };
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ }
37
55
  export function splitWindowPane(cwd, command) {
38
56
  const originalPane = getCurrentPaneId();
39
- const paneId = tmuxCmd([
40
- "split-window",
41
- "-h",
42
- "-P",
43
- "-F",
44
- "#{pane_id}",
45
- "-c",
46
- cwd,
47
- command,
48
- ]);
57
+ const paneSize = getCurrentPaneSize(originalPane);
58
+ const direction = chooseTmuxSplitDirection(paneSize?.width ?? 0, paneSize?.height ?? 0);
59
+ const paneId = tmuxCmd(buildTmuxSplitWindowArgs(cwd, command, direction, originalPane));
49
60
  return { paneId, originalPane };
50
61
  }
51
62
  export function killAgentPane(paneId, originalPane) {
@@ -64,9 +75,23 @@ export function killAgentPane(paneId, originalPane) {
64
75
  }
65
76
  }
66
77
  }
67
- /** Inject keys into a running subagent pane (steer / follow-up). */
78
+ /** Inject text into a running subagent pane (steer / follow-up). */
68
79
  export function tmuxSteerPane(paneId, message) {
69
- const escaped = message.replace(/'/g, `'\"'\"'`);
70
- tmuxCmd(["send-keys", "-t", paneId, "-l", escaped]);
80
+ const bufferName = `pi-task-steer-${process.pid}-${Date.now()}`;
81
+ try {
82
+ execFileSync("tmux", ["load-buffer", "-b", bufferName, "-"], {
83
+ input: message,
84
+ stdio: ["pipe", "pipe", "pipe"],
85
+ });
86
+ tmuxCmd(["paste-buffer", "-b", bufferName, "-t", paneId]);
87
+ }
88
+ finally {
89
+ try {
90
+ tmuxCmd(["delete-buffer", "-b", bufferName]);
91
+ }
92
+ catch {
93
+ /* ignore */
94
+ }
95
+ }
71
96
  tmuxCmd(["send-keys", "-t", paneId, "Enter"]);
72
97
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyhuynhgiabuu/pi-task",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Delegating task/subagent extension for Pi: foreground/background subagents, widgets, tmux observability, SDK fallback.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",