@henryqw/pi-herdr-clone 0.1.3 → 0.2.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/README.md +26 -14
- package/extensions/clone-tab.ts +209 -72
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `@henryqw/pi-herdr-clone`
|
|
2
2
|
|
|
3
|
-
Pi extension that clones the current conversation path into a new Pi process in a new tab of the current Herdr workspace. Requires Pi Coding Agent 0.84.x (minimum 0.84.2) and a Pi session running inside Herdr.
|
|
3
|
+
Pi extension that clones the current conversation path into a new Pi process in a new tab of the current Herdr workspace, or into a new Herdr Git worktree workspace. Requires Pi Coding Agent 0.84.x (minimum 0.84.2) and a Pi session running inside Herdr.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -8,25 +8,37 @@ Pi extension that clones the current conversation path into a new Pi process in
|
|
|
8
8
|
pi install npm:@henryqw/pi-herdr-clone
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
## Use
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
| Surface | Type | Purpose |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| `/clone-tab` | command | Clone the current conversation into a new tab of the current Herdr workspace. |
|
|
16
|
+
| `/clone-worktree` | command | Clone the current conversation into a new Herdr Git worktree workspace. |
|
|
17
|
+
|
|
18
|
+
Both commands wait until Pi is idle, then validate the current Herdr pane (`HERDR_ENV=1`, `HERDR_PANE_ID`), the persisted session file, and the current session leaf. They copy only the active root-to-leaf path into a new persisted session file: sibling branches are excluded and the original Pi session is not switched. Neither command has configuration.
|
|
19
|
+
|
|
20
|
+
### `/clone-tab` behavior
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
1. Creates an unfocused Herdr tab in the current workspace with the current working directory.
|
|
23
|
+
2. Starts Pi in the tab's root pane with `--session <absolute-clone-file>`.
|
|
24
|
+
3. Focuses the new tab after Pi starts successfully.
|
|
18
25
|
|
|
19
|
-
|
|
26
|
+
### `/clone-worktree` behavior
|
|
20
27
|
|
|
21
|
-
1.
|
|
22
|
-
2. Copies
|
|
23
|
-
3.
|
|
24
|
-
4.
|
|
25
|
-
5. Focuses the new tab after Pi starts successfully.
|
|
28
|
+
1. Creates a Git worktree-backed workspace with `herdr worktree create --workspace <current-workspace> --no-focus`; Herdr creates the branch from `HEAD` unless the name exists, checks out the worktree under its configured `worktrees.directory`, and opens it as a grouped workspace.
|
|
29
|
+
2. Copies the active path into a clone session stamped with the fresh checkout path as its working directory.
|
|
30
|
+
3. Starts Pi in the new workspace's root pane (whose shell runs inside the checkout) with `--session <absolute-clone-file>`.
|
|
31
|
+
4. Focuses the new tab after Pi starts successfully.
|
|
26
32
|
|
|
27
|
-
|
|
33
|
+
### Failure semantics
|
|
28
34
|
|
|
29
|
-
If
|
|
35
|
+
If target creation fails outright, no clone session is kept or created. A killed or incomplete creation response is ambiguous because Herdr may have retained partial state; the error reports every identifier returned so far and suggests inspecting `herdr workspace list`. Once agent start is attempted, the target tab, panes, and session file are retained because the launch outcome can be unknown; the error reports any known IDs for recovery. A later focus failure is shown as a warning and does not report the already-started clone as failed.
|
|
36
|
+
|
|
37
|
+
## Remove
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pi remove npm:@henryqw/pi-herdr-clone
|
|
41
|
+
```
|
|
30
42
|
|
|
31
43
|
## Development
|
|
32
44
|
|
package/extensions/clone-tab.ts
CHANGED
|
@@ -5,8 +5,16 @@ import { setTimeout as delay } from "node:timers/promises";
|
|
|
5
5
|
import {
|
|
6
6
|
SessionManager,
|
|
7
7
|
type ExtensionAPI,
|
|
8
|
+
type ExtensionCommandContext,
|
|
8
9
|
} from "@earendil-works/pi-coding-agent";
|
|
9
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
createHerdrClient,
|
|
12
|
+
herdrCommandFailure,
|
|
13
|
+
hasHerdrErrorCode,
|
|
14
|
+
withWorktreeLock,
|
|
15
|
+
type HerdrClient,
|
|
16
|
+
type HerdrExecResult,
|
|
17
|
+
} from "@henryqw/pi-herdr";
|
|
10
18
|
|
|
11
19
|
type WorkspaceInfo = {
|
|
12
20
|
workspace_id?: unknown;
|
|
@@ -20,6 +28,112 @@ function requiredString(value: unknown, label: string): string {
|
|
|
20
28
|
return value;
|
|
21
29
|
}
|
|
22
30
|
|
|
31
|
+
type SourceContext = {
|
|
32
|
+
sessionFile: string;
|
|
33
|
+
leafId: string;
|
|
34
|
+
workspaceId: string;
|
|
35
|
+
checkout: string | undefined;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
async function resolveSource(
|
|
39
|
+
commandName: string,
|
|
40
|
+
herdr: HerdrClient<{ cwd: string }>,
|
|
41
|
+
ctx: ExtensionCommandContext,
|
|
42
|
+
): Promise<SourceContext> {
|
|
43
|
+
if (process.env.HERDR_ENV !== "1") {
|
|
44
|
+
throw new Error(`/${commandName} requires the current Pi session inside Herdr (HERDR_ENV=1).`);
|
|
45
|
+
}
|
|
46
|
+
const requestedPaneId = requiredString(process.env.HERDR_PANE_ID, "HERDR_PANE_ID");
|
|
47
|
+
const currentSessionFile = requiredString(
|
|
48
|
+
ctx.sessionManager.getSessionFile(),
|
|
49
|
+
"Persisted Pi session file",
|
|
50
|
+
);
|
|
51
|
+
const leafId = requiredString(ctx.sessionManager.getLeafId(), "Current Pi session leaf");
|
|
52
|
+
const sessionFile = resolve(currentSessionFile);
|
|
53
|
+
let sourceStat;
|
|
54
|
+
try {
|
|
55
|
+
sourceStat = await stat(sessionFile);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
throw new Error(`Persisted Pi session file does not exist: ${sessionFile}`, { cause: error });
|
|
58
|
+
}
|
|
59
|
+
if (!sourceStat.isFile()) throw new Error(`Persisted Pi session path is not a file: ${sessionFile}`);
|
|
60
|
+
|
|
61
|
+
const paneResponse = await herdr.json(["pane", "get", requestedPaneId], { cwd: ctx.cwd });
|
|
62
|
+
const pane = (paneResponse as { result?: { pane?: { pane_id?: unknown; workspace_id?: unknown } } }).result?.pane;
|
|
63
|
+
requiredString(pane?.pane_id, "Herdr pane response pane_id");
|
|
64
|
+
const workspaceId = requiredString(pane?.workspace_id, "Herdr pane response workspace_id");
|
|
65
|
+
const workspaceResponse = await herdr.json(["workspace", "get", workspaceId], { cwd: ctx.cwd });
|
|
66
|
+
const workspace = (workspaceResponse as { result?: { workspace?: WorkspaceInfo } }).result?.workspace;
|
|
67
|
+
if (requiredString(workspace?.workspace_id, "Herdr workspace response workspace_id") !== workspaceId) {
|
|
68
|
+
throw new Error(`Herdr workspace response did not match ${workspaceId}.`);
|
|
69
|
+
}
|
|
70
|
+
const checkout = workspace?.worktree == null
|
|
71
|
+
? undefined
|
|
72
|
+
: requiredString(workspace.worktree.checkout_path, "Herdr workspace response checkout_path");
|
|
73
|
+
return { sessionFile, leafId, workspaceId, checkout };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function createBranchedClone(
|
|
77
|
+
ctx: ExtensionCommandContext,
|
|
78
|
+
source: SourceContext,
|
|
79
|
+
cwd: string,
|
|
80
|
+
): Promise<string> {
|
|
81
|
+
const session = SessionManager.open(source.sessionFile, ctx.sessionManager.getSessionDir(), cwd);
|
|
82
|
+
const createdClone = session.createBranchedSession(source.leafId);
|
|
83
|
+
if (!createdClone) throw new Error("Pi did not create a persisted clone session file.");
|
|
84
|
+
const cloneFile = resolve(createdClone);
|
|
85
|
+
let cloneStat;
|
|
86
|
+
try {
|
|
87
|
+
cloneStat = await stat(cloneFile);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
throw new Error(`Pi clone session file was not created: ${cloneFile}`, { cause: error });
|
|
90
|
+
}
|
|
91
|
+
if (!cloneStat.isFile()) throw new Error(`Pi clone session path is not a file: ${cloneFile}`);
|
|
92
|
+
return cloneFile;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function discardCloneOrAggregate(cloneFile: string, error: Error): Promise<never> {
|
|
96
|
+
try {
|
|
97
|
+
await unlink(cloneFile);
|
|
98
|
+
} catch (cleanupError) {
|
|
99
|
+
throw new AggregateError(
|
|
100
|
+
[error, cleanupError],
|
|
101
|
+
`${error.message} Clone cleanup also failed for ${cloneFile}: ${errorMessage(cleanupError)}`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function launchCloneAgent(
|
|
108
|
+
herdr: HerdrClient<{ cwd: string }>,
|
|
109
|
+
ctx: ExtensionCommandContext,
|
|
110
|
+
rootPaneId: string,
|
|
111
|
+
cloneFile: string,
|
|
112
|
+
retained: string,
|
|
113
|
+
): Promise<string> {
|
|
114
|
+
const agentName = `clone-${randomUUID().replaceAll("-", "").slice(0, 24)}`;
|
|
115
|
+
const startArgs = [
|
|
116
|
+
"agent", "start", agentName, "--kind", "pi", "--pane", rootPaneId,
|
|
117
|
+
"--", "--session", cloneFile,
|
|
118
|
+
];
|
|
119
|
+
try {
|
|
120
|
+
for (let attempt = 1; attempt <= 5; attempt += 1) {
|
|
121
|
+
const result = await herdr.exec(startArgs, { cwd: ctx.cwd });
|
|
122
|
+
if (result.code === 0 && !result.killed) return agentName;
|
|
123
|
+
if (!hasHerdrErrorCode(result, "agent_pane_busy") || attempt === 5) {
|
|
124
|
+
throw new Error(herdrCommandFailure(startArgs, result));
|
|
125
|
+
}
|
|
126
|
+
await delay(250);
|
|
127
|
+
}
|
|
128
|
+
throw new Error("Herdr agent start retry loop exited unexpectedly.");
|
|
129
|
+
} catch (error) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`Clone launch could not be confirmed after starting agent ${agentName}; retained ${retained}: ${errorMessage(error)}`,
|
|
132
|
+
{ cause: error },
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
23
137
|
export default function herdrCloneExtension(pi: ExtensionAPI): void {
|
|
24
138
|
const herdr = createHerdrClient<{ cwd: string }>((command, args, options) =>
|
|
25
139
|
pi.exec(command, [...args], options));
|
|
@@ -28,67 +142,17 @@ export default function herdrCloneExtension(pi: ExtensionAPI): void {
|
|
|
28
142
|
description: "Clone the current conversation path into a new Herdr tab",
|
|
29
143
|
handler: async (_args, ctx) => {
|
|
30
144
|
await ctx.waitForIdle();
|
|
31
|
-
|
|
32
|
-
if (process.env.HERDR_ENV !== "1") {
|
|
33
|
-
throw new Error("/clone-tab requires the current Pi session inside Herdr (HERDR_ENV=1).");
|
|
34
|
-
}
|
|
35
|
-
const requestedPaneId = requiredString(process.env.HERDR_PANE_ID, "HERDR_PANE_ID");
|
|
36
|
-
const currentSessionFile = requiredString(
|
|
37
|
-
ctx.sessionManager.getSessionFile(),
|
|
38
|
-
"Persisted Pi session file",
|
|
39
|
-
);
|
|
40
|
-
const leafId = requiredString(ctx.sessionManager.getLeafId(), "Current Pi session leaf");
|
|
41
|
-
const sessionFile = resolve(currentSessionFile);
|
|
42
|
-
let sourceStat;
|
|
43
|
-
try {
|
|
44
|
-
sourceStat = await stat(sessionFile);
|
|
45
|
-
} catch (error) {
|
|
46
|
-
throw new Error(`Persisted Pi session file does not exist: ${sessionFile}`, { cause: error });
|
|
47
|
-
}
|
|
48
|
-
if (!sourceStat.isFile()) throw new Error(`Persisted Pi session path is not a file: ${sessionFile}`);
|
|
49
|
-
|
|
50
|
-
const paneResponse = await herdr.json(["pane", "get", requestedPaneId], { cwd: ctx.cwd });
|
|
51
|
-
const pane = (paneResponse as { result?: { pane?: { pane_id?: unknown; workspace_id?: unknown } } }).result?.pane;
|
|
52
|
-
requiredString(pane?.pane_id, "Herdr pane response pane_id");
|
|
53
|
-
const workspaceId = requiredString(pane?.workspace_id, "Herdr pane response workspace_id");
|
|
54
|
-
const workspaceResponse = await herdr.json(["workspace", "get", workspaceId], { cwd: ctx.cwd });
|
|
55
|
-
const workspace = (workspaceResponse as { result?: { workspace?: WorkspaceInfo } }).result?.workspace;
|
|
56
|
-
if (requiredString(workspace?.workspace_id, "Herdr workspace response workspace_id") !== workspaceId) {
|
|
57
|
-
throw new Error(`Herdr workspace response did not match ${workspaceId}.`);
|
|
58
|
-
}
|
|
59
|
-
const checkout = workspace?.worktree == null
|
|
60
|
-
? undefined
|
|
61
|
-
: requiredString(workspace.worktree.checkout_path, "Herdr workspace response checkout_path");
|
|
145
|
+
const source = await resolveSource("clone-tab", herdr, ctx);
|
|
62
146
|
const mutate = async (): Promise<{ createdTab: HerdrExecResult; cloneFile: string }> => {
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
if (!createdClone) throw new Error("Pi did not create a persisted clone session file.");
|
|
66
|
-
const cloneFile = resolve(createdClone);
|
|
67
|
-
let cloneStat;
|
|
68
|
-
try {
|
|
69
|
-
cloneStat = await stat(cloneFile);
|
|
70
|
-
} catch (error) {
|
|
71
|
-
throw new Error(`Pi clone session file was not created: ${cloneFile}`, { cause: error });
|
|
72
|
-
}
|
|
73
|
-
if (!cloneStat.isFile()) throw new Error(`Pi clone session path is not a file: ${cloneFile}`);
|
|
74
|
-
|
|
75
|
-
const tabCreateArgs = ["tab", "create", "--workspace", workspaceId, "--cwd", ctx.cwd, "--no-focus"] as const;
|
|
147
|
+
const cloneFile = await createBranchedClone(ctx, source, ctx.cwd);
|
|
148
|
+
const tabCreateArgs = ["tab", "create", "--workspace", source.workspaceId, "--cwd", ctx.cwd, "--no-focus"] as const;
|
|
76
149
|
const createdTab = await herdr.exec(tabCreateArgs, { cwd: ctx.cwd });
|
|
77
150
|
if (createdTab.code !== 0 || createdTab.killed) {
|
|
78
|
-
|
|
79
|
-
try {
|
|
80
|
-
await unlink(cloneFile);
|
|
81
|
-
} catch (cleanupError) {
|
|
82
|
-
throw new AggregateError(
|
|
83
|
-
[createError, cleanupError],
|
|
84
|
-
`${createError.message} Clone cleanup also failed for ${cloneFile}: ${errorMessage(cleanupError)}`,
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
throw createError;
|
|
151
|
+
await discardCloneOrAggregate(cloneFile, new Error(herdrCommandFailure(tabCreateArgs, createdTab)));
|
|
88
152
|
}
|
|
89
153
|
return { createdTab, cloneFile };
|
|
90
154
|
};
|
|
91
|
-
const { createdTab, cloneFile } = checkout ? await withWorktreeLock(checkout, mutate) : await mutate();
|
|
155
|
+
const { createdTab, cloneFile } = source.checkout ? await withWorktreeLock(source.checkout, mutate) : await mutate();
|
|
92
156
|
let tabId: string | undefined;
|
|
93
157
|
let rootPaneId: string | undefined;
|
|
94
158
|
try {
|
|
@@ -107,38 +171,111 @@ export default function herdrCloneExtension(pi: ExtensionAPI): void {
|
|
|
107
171
|
{ cause: error },
|
|
108
172
|
);
|
|
109
173
|
}
|
|
110
|
-
const agentName = `
|
|
111
|
-
|
|
112
|
-
"agent", "start", agentName, "--kind", "pi", "--pane", rootPaneId,
|
|
113
|
-
"--", "--session", cloneFile,
|
|
114
|
-
];
|
|
174
|
+
const agentName = await launchCloneAgent(herdr, ctx, rootPaneId!, cloneFile, `Herdr tab ${tabId}, root pane ${rootPaneId}, and session ${cloneFile}`);
|
|
175
|
+
|
|
115
176
|
try {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
177
|
+
await herdr.run(["tab", "focus", tabId!], { cwd: ctx.cwd });
|
|
178
|
+
} catch (error) {
|
|
179
|
+
ctx.ui.notify(
|
|
180
|
+
`Clone agent ${agentName} started in Herdr tab ${tabId} (root pane ${rootPaneId}), but focus failed: ${errorMessage(error)}`,
|
|
181
|
+
"warning",
|
|
182
|
+
);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
ctx.ui.notify(
|
|
186
|
+
`Cloned current conversation into Herdr tab ${tabId} (root pane ${rootPaneId}, agent ${agentName}).`,
|
|
187
|
+
"info",
|
|
188
|
+
);
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
pi.registerCommand("clone-worktree", {
|
|
193
|
+
description: "Clone the current conversation into Pi in a new Herdr Git worktree",
|
|
194
|
+
handler: async (_args, ctx) => {
|
|
195
|
+
await ctx.waitForIdle();
|
|
196
|
+
const source = await resolveSource("clone-worktree", herdr, ctx);
|
|
197
|
+
|
|
198
|
+
// Create the worktree before the clone so the session header can be
|
|
199
|
+
// stamped with the fresh checkout cwd. A killed or incomplete create is
|
|
200
|
+
// ambiguous: Herdr may have retained partial worktree state.
|
|
201
|
+
const worktreeCreateArgs = ["worktree", "create", "--workspace", source.workspaceId, "--no-focus"] as const;
|
|
202
|
+
const createdWorktree = await herdr.exec(worktreeCreateArgs, { cwd: ctx.cwd });
|
|
203
|
+
if (createdWorktree.code !== 0 && !createdWorktree.killed) {
|
|
204
|
+
throw new Error(herdrCommandFailure(worktreeCreateArgs, createdWorktree));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let workspaceId: string | undefined;
|
|
208
|
+
let tabId: string | undefined;
|
|
209
|
+
let rootPaneId: string | undefined;
|
|
210
|
+
let checkoutPath: string | undefined;
|
|
211
|
+
try {
|
|
212
|
+
const response: unknown = JSON.parse(createdWorktree.stdout);
|
|
213
|
+
if (!response || typeof response !== "object" || Array.isArray(response)) {
|
|
214
|
+
throw new Error("Herdr worktree create returned invalid JSON");
|
|
215
|
+
}
|
|
216
|
+
const result = (response as {
|
|
217
|
+
result?: {
|
|
218
|
+
workspace?: { workspace_id?: unknown };
|
|
219
|
+
tab?: { tab_id?: unknown };
|
|
220
|
+
root_pane?: { pane_id?: unknown };
|
|
221
|
+
worktree?: { checkout_path?: unknown };
|
|
222
|
+
};
|
|
223
|
+
}).result;
|
|
224
|
+
// Collect every returned identifier before validating so recovery
|
|
225
|
+
// keeps all known IDs even when an earlier field is missing.
|
|
226
|
+
workspaceId = typeof result?.workspace?.workspace_id === "string" ? result.workspace.workspace_id : undefined;
|
|
227
|
+
tabId = typeof result?.tab?.tab_id === "string" ? result.tab.tab_id : undefined;
|
|
228
|
+
rootPaneId = typeof result?.root_pane?.pane_id === "string" ? result.root_pane.pane_id : undefined;
|
|
229
|
+
checkoutPath = typeof result?.worktree?.checkout_path === "string" ? result.worktree.checkout_path : undefined;
|
|
230
|
+
const missing = [
|
|
231
|
+
[workspaceId, "workspace_id"],
|
|
232
|
+
[tabId, "tab_id"],
|
|
233
|
+
[rootPaneId, "root_pane.pane_id"],
|
|
234
|
+
[checkoutPath, "worktree.checkout_path"],
|
|
235
|
+
].filter(([value]) => !value).map(([, label]) => label);
|
|
236
|
+
if (missing.length > 0) {
|
|
237
|
+
throw new Error(`Herdr worktree create response is missing ${missing.join(", ")}.`);
|
|
123
238
|
}
|
|
239
|
+
} catch (error) {
|
|
240
|
+
const known = [
|
|
241
|
+
workspaceId && `workspace ${workspaceId}`,
|
|
242
|
+
tabId && `tab ${tabId}`,
|
|
243
|
+
rootPaneId && `root pane ${rootPaneId}`,
|
|
244
|
+
checkoutPath && `checkout ${checkoutPath}`,
|
|
245
|
+
].filter(Boolean).join(", ");
|
|
246
|
+
throw new Error(
|
|
247
|
+
`Clone could not be confirmed after creating a Herdr worktree${known ? ` (${known})` : ""}; Herdr may have retained a partial worktree workspace, inspect herdr workspace list: ${errorMessage(error)}`,
|
|
248
|
+
{ cause: error },
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
let cloneFile: string;
|
|
253
|
+
try {
|
|
254
|
+
cloneFile = source.checkout
|
|
255
|
+
? await withWorktreeLock(source.checkout, () => createBranchedClone(ctx, source, checkoutPath!))
|
|
256
|
+
: await createBranchedClone(ctx, source, checkoutPath!);
|
|
124
257
|
} catch (error) {
|
|
125
258
|
throw new Error(
|
|
126
|
-
`Clone
|
|
259
|
+
`Clone session could not be created for Herdr worktree workspace ${workspaceId} (tab ${tabId}, checkout ${checkoutPath}); retained worktree without a clone session: ${errorMessage(error)}`,
|
|
127
260
|
{ cause: error },
|
|
128
261
|
);
|
|
129
262
|
}
|
|
263
|
+
const agentName = await launchCloneAgent(
|
|
264
|
+
herdr, ctx, rootPaneId!, cloneFile,
|
|
265
|
+
`Herdr workspace ${workspaceId}, tab ${tabId}, root pane ${rootPaneId}, and session ${cloneFile}`,
|
|
266
|
+
);
|
|
130
267
|
|
|
131
268
|
try {
|
|
132
|
-
await herdr.run(["tab", "focus", tabId], { cwd: ctx.cwd });
|
|
269
|
+
await herdr.run(["tab", "focus", tabId!], { cwd: ctx.cwd });
|
|
133
270
|
} catch (error) {
|
|
134
271
|
ctx.ui.notify(
|
|
135
|
-
`Clone agent ${agentName} started in Herdr tab ${tabId}
|
|
272
|
+
`Clone agent ${agentName} started in Herdr worktree workspace ${workspaceId} (tab ${tabId}, checkout ${checkoutPath}), but focus failed: ${errorMessage(error)}`,
|
|
136
273
|
"warning",
|
|
137
274
|
);
|
|
138
275
|
return;
|
|
139
276
|
}
|
|
140
277
|
ctx.ui.notify(
|
|
141
|
-
`Cloned current conversation into Herdr tab ${tabId}
|
|
278
|
+
`Cloned current conversation into Herdr worktree workspace ${workspaceId} (tab ${tabId}, checkout ${checkoutPath}, agent ${agentName}).`,
|
|
142
279
|
"info",
|
|
143
280
|
);
|
|
144
281
|
},
|