@henryqw/pi-herdr-clone 0.1.2 → 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 +219 -67
- package/package.json +2 -2
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,21 @@ 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";
|
|
18
|
+
|
|
19
|
+
type WorkspaceInfo = {
|
|
20
|
+
workspace_id?: unknown;
|
|
21
|
+
worktree?: { checkout_path?: unknown } | null;
|
|
22
|
+
};
|
|
10
23
|
|
|
11
24
|
const errorMessage = (error: unknown) => error instanceof Error ? error.message : String(error);
|
|
12
25
|
|
|
@@ -15,6 +28,112 @@ function requiredString(value: unknown, label: string): string {
|
|
|
15
28
|
return value;
|
|
16
29
|
}
|
|
17
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
|
+
|
|
18
137
|
export default function herdrCloneExtension(pi: ExtensionAPI): void {
|
|
19
138
|
const herdr = createHerdrClient<{ cwd: string }>((command, args, options) =>
|
|
20
139
|
pi.exec(command, [...args], options));
|
|
@@ -23,57 +142,17 @@ export default function herdrCloneExtension(pi: ExtensionAPI): void {
|
|
|
23
142
|
description: "Clone the current conversation path into a new Herdr tab",
|
|
24
143
|
handler: async (_args, ctx) => {
|
|
25
144
|
await ctx.waitForIdle();
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"Persisted Pi session file",
|
|
34
|
-
);
|
|
35
|
-
const leafId = requiredString(ctx.sessionManager.getLeafId(), "Current Pi session leaf");
|
|
36
|
-
const sessionFile = resolve(currentSessionFile);
|
|
37
|
-
let sourceStat;
|
|
38
|
-
try {
|
|
39
|
-
sourceStat = await stat(sessionFile);
|
|
40
|
-
} catch (error) {
|
|
41
|
-
throw new Error(`Persisted Pi session file does not exist: ${sessionFile}`, { cause: error });
|
|
42
|
-
}
|
|
43
|
-
if (!sourceStat.isFile()) throw new Error(`Persisted Pi session path is not a file: ${sessionFile}`);
|
|
44
|
-
|
|
45
|
-
const paneResponse = await herdr.json(["pane", "get", requestedPaneId], { cwd: ctx.cwd });
|
|
46
|
-
const pane = (paneResponse as { result?: { pane?: { pane_id?: unknown; workspace_id?: unknown } } }).result?.pane;
|
|
47
|
-
requiredString(pane?.pane_id, "Herdr pane response pane_id");
|
|
48
|
-
const workspaceId = requiredString(pane?.workspace_id, "Herdr pane response workspace_id");
|
|
49
|
-
|
|
50
|
-
const session = SessionManager.open(sessionFile, ctx.sessionManager.getSessionDir(), ctx.cwd);
|
|
51
|
-
const createdClone = session.createBranchedSession(leafId);
|
|
52
|
-
if (!createdClone) throw new Error("Pi did not create a persisted clone session file.");
|
|
53
|
-
const cloneFile = resolve(createdClone);
|
|
54
|
-
let cloneStat;
|
|
55
|
-
try {
|
|
56
|
-
cloneStat = await stat(cloneFile);
|
|
57
|
-
} catch (error) {
|
|
58
|
-
throw new Error(`Pi clone session file was not created: ${cloneFile}`, { cause: error });
|
|
59
|
-
}
|
|
60
|
-
if (!cloneStat.isFile()) throw new Error(`Pi clone session path is not a file: ${cloneFile}`);
|
|
61
|
-
|
|
62
|
-
const tabCreateArgs = ["tab", "create", "--workspace", workspaceId, "--cwd", ctx.cwd, "--no-focus"] as const;
|
|
63
|
-
const createdTab = await herdr.exec(tabCreateArgs, { cwd: ctx.cwd });
|
|
64
|
-
if (createdTab.code !== 0 || createdTab.killed) {
|
|
65
|
-
const createError = new Error(herdrCommandFailure(tabCreateArgs, createdTab));
|
|
66
|
-
try {
|
|
67
|
-
await unlink(cloneFile);
|
|
68
|
-
} catch (cleanupError) {
|
|
69
|
-
throw new AggregateError(
|
|
70
|
-
[createError, cleanupError],
|
|
71
|
-
`${createError.message} Clone cleanup also failed for ${cloneFile}: ${errorMessage(cleanupError)}`,
|
|
72
|
-
);
|
|
145
|
+
const source = await resolveSource("clone-tab", herdr, ctx);
|
|
146
|
+
const mutate = async (): Promise<{ createdTab: HerdrExecResult; cloneFile: string }> => {
|
|
147
|
+
const cloneFile = await createBranchedClone(ctx, source, ctx.cwd);
|
|
148
|
+
const tabCreateArgs = ["tab", "create", "--workspace", source.workspaceId, "--cwd", ctx.cwd, "--no-focus"] as const;
|
|
149
|
+
const createdTab = await herdr.exec(tabCreateArgs, { cwd: ctx.cwd });
|
|
150
|
+
if (createdTab.code !== 0 || createdTab.killed) {
|
|
151
|
+
await discardCloneOrAggregate(cloneFile, new Error(herdrCommandFailure(tabCreateArgs, createdTab)));
|
|
73
152
|
}
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
153
|
+
return { createdTab, cloneFile };
|
|
154
|
+
};
|
|
155
|
+
const { createdTab, cloneFile } = source.checkout ? await withWorktreeLock(source.checkout, mutate) : await mutate();
|
|
77
156
|
let tabId: string | undefined;
|
|
78
157
|
let rootPaneId: string | undefined;
|
|
79
158
|
try {
|
|
@@ -92,38 +171,111 @@ export default function herdrCloneExtension(pi: ExtensionAPI): void {
|
|
|
92
171
|
{ cause: error },
|
|
93
172
|
);
|
|
94
173
|
}
|
|
95
|
-
const agentName = `
|
|
96
|
-
|
|
97
|
-
"agent", "start", agentName, "--kind", "pi", "--pane", rootPaneId,
|
|
98
|
-
"--", "--session", cloneFile,
|
|
99
|
-
];
|
|
174
|
+
const agentName = await launchCloneAgent(herdr, ctx, rootPaneId!, cloneFile, `Herdr tab ${tabId}, root pane ${rootPaneId}, and session ${cloneFile}`);
|
|
175
|
+
|
|
100
176
|
try {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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(", ")}.`);
|
|
108
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!);
|
|
109
257
|
} catch (error) {
|
|
110
258
|
throw new Error(
|
|
111
|
-
`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)}`,
|
|
112
260
|
{ cause: error },
|
|
113
261
|
);
|
|
114
262
|
}
|
|
263
|
+
const agentName = await launchCloneAgent(
|
|
264
|
+
herdr, ctx, rootPaneId!, cloneFile,
|
|
265
|
+
`Herdr workspace ${workspaceId}, tab ${tabId}, root pane ${rootPaneId}, and session ${cloneFile}`,
|
|
266
|
+
);
|
|
115
267
|
|
|
116
268
|
try {
|
|
117
|
-
await herdr.run(["tab", "focus", tabId], { cwd: ctx.cwd });
|
|
269
|
+
await herdr.run(["tab", "focus", tabId!], { cwd: ctx.cwd });
|
|
118
270
|
} catch (error) {
|
|
119
271
|
ctx.ui.notify(
|
|
120
|
-
`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)}`,
|
|
121
273
|
"warning",
|
|
122
274
|
);
|
|
123
275
|
return;
|
|
124
276
|
}
|
|
125
277
|
ctx.ui.notify(
|
|
126
|
-
`Cloned current conversation into Herdr tab ${tabId}
|
|
278
|
+
`Cloned current conversation into Herdr worktree workspace ${workspaceId} (tab ${tabId}, checkout ${checkoutPath}, agent ${agentName}).`,
|
|
127
279
|
"info",
|
|
128
280
|
);
|
|
129
281
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@henryqw/pi-herdr-clone",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Clone the current Pi conversation path into a new Herdr tab.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@earendil-works/pi-coding-agent": "^0.84.2"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@henryqw/pi-herdr": "^0.
|
|
31
|
+
"@henryqw/pi-herdr": "^0.2.0"
|
|
32
32
|
},
|
|
33
33
|
"repository": {
|
|
34
34
|
"type": "git",
|