pum-agent 0.2.20-beta.1 → 0.2.21-beta.1
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 +8 -1
- package/package.json +1 -1
- package/src/app.tsx +31 -3
- package/src/index.tsx +1 -1
- package/src/main.tsx +15 -3
- package/src/relocation.ts +30 -0
package/README.md
CHANGED
|
@@ -38,7 +38,14 @@ empty prompt to see every control.
|
|
|
38
38
|
|
|
39
39
|
</details>
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
<details>
|
|
42
|
+
<summary><strong>Controls panel</strong></summary>
|
|
43
|
+
|
|
44
|
+

|
|
45
|
+
|
|
46
|
+
</details>
|
|
47
|
+
|
|
48
|
+
These are real OpenTUI renders, not mockups. `bun run scripts/capture-screenshots.tsx`
|
|
42
49
|
drives the actual TUI and converts the captured cells to SVG.
|
|
43
50
|
|
|
44
51
|
## What it does
|
package/package.json
CHANGED
package/src/app.tsx
CHANGED
|
@@ -626,6 +626,7 @@ export function App({
|
|
|
626
626
|
sandboxWarningSource,
|
|
627
627
|
forcedSandboxMode,
|
|
628
628
|
forcedCheckPaths = [],
|
|
629
|
+
initialRelocation,
|
|
629
630
|
initialCwd,
|
|
630
631
|
userBashOperations,
|
|
631
632
|
}: {
|
|
@@ -666,6 +667,8 @@ export function App({
|
|
|
666
667
|
/** Process-local sandbox floor that does not overwrite persisted user settings. */
|
|
667
668
|
forcedSandboxMode?: NonNullable<PumSettings["sandboxMode"]>;
|
|
668
669
|
forcedCheckPaths?: readonly string[];
|
|
670
|
+
/** Relocation created before the TUI mounted, such as a `pum worktree` launch. */
|
|
671
|
+
initialRelocation?: RelocationRecord;
|
|
669
672
|
/** Directory the session starts in. Defaults to the process working directory. */
|
|
670
673
|
initialCwd?: string;
|
|
671
674
|
/** User commands bypass Check mode but use this native sandbox execution path. */
|
|
@@ -1990,7 +1993,7 @@ export function App({
|
|
|
1990
1993
|
}, [activeAgent?.id, activeAgent?.status, activeAgent?.runStartedAt, visibleBusy]);
|
|
1991
1994
|
|
|
1992
1995
|
const [relocation, setRelocation] = useState(
|
|
1993
|
-
() => loadRelocation(initialSession.sessionFile),
|
|
1996
|
+
() => loadRelocation(initialSession.sessionFile) ?? initialRelocation ?? null,
|
|
1994
1997
|
);
|
|
1995
1998
|
const relocationRef = useRef(relocation);
|
|
1996
1999
|
relocationRef.current = relocation;
|
|
@@ -2135,7 +2138,6 @@ export function App({
|
|
|
2135
2138
|
if (!record || record.location !== "worktree") return;
|
|
2136
2139
|
if (restoredRelocationRef.current === record.id) return;
|
|
2137
2140
|
restoredRelocationRef.current = record.id;
|
|
2138
|
-
if (pathIdentity(cwdRef.current) === pathIdentity(record.worktreePath)) return;
|
|
2139
2141
|
void (async () => {
|
|
2140
2142
|
const trusted = relocationPathsTrusted(record, {
|
|
2141
2143
|
worktreeExists: existsSync(record.worktreePath),
|
|
@@ -2143,16 +2145,42 @@ export function App({
|
|
|
2143
2145
|
sourceRoot: record.sourceRoot,
|
|
2144
2146
|
});
|
|
2145
2147
|
if (!trusted) {
|
|
2148
|
+
const alreadyInWorktree = pathIdentity(cwdRef.current) === pathIdentity(record.worktreePath);
|
|
2149
|
+
if (alreadyInWorktree && onRelocate) {
|
|
2150
|
+
const moved = await onRelocate(record.sourceRoot).catch(() => null);
|
|
2151
|
+
if (moved) {
|
|
2152
|
+
applyRelocation({
|
|
2153
|
+
...record,
|
|
2154
|
+
generation: record.generation + 1,
|
|
2155
|
+
location: "source",
|
|
2156
|
+
updatedAt: Date.now(),
|
|
2157
|
+
});
|
|
2158
|
+
appendMainLine({
|
|
2159
|
+
kind: "text",
|
|
2160
|
+
role: "error",
|
|
2161
|
+
text: `worktree ${record.name} no longer matches ${record.branch}; returned to ${record.sourceRoot}`,
|
|
2162
|
+
});
|
|
2163
|
+
return;
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2146
2166
|
relocationRef.current = null;
|
|
2147
2167
|
setRelocation(null);
|
|
2148
2168
|
saveRelocation(session.sessionFile, null);
|
|
2149
2169
|
appendMainLine({
|
|
2150
2170
|
kind: "text",
|
|
2151
2171
|
role: "error",
|
|
2152
|
-
text:
|
|
2172
|
+
text: alreadyInWorktree
|
|
2173
|
+
? `worktree ${record.name} no longer matches ${record.branch}; its relocation record was removed`
|
|
2174
|
+
: `worktree ${record.name} no longer matches ${record.branch}; staying in ${record.sourceRoot}`,
|
|
2153
2175
|
});
|
|
2154
2176
|
return;
|
|
2155
2177
|
}
|
|
2178
|
+
// A CLI worktree launch resumes in the recorded checkout already. It
|
|
2179
|
+
// still needs trust validation and the source root in the live roots.
|
|
2180
|
+
if (pathIdentity(cwdRef.current) === pathIdentity(record.worktreePath)) {
|
|
2181
|
+
applyRelocation(record);
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2156
2184
|
if (!onRelocate) return;
|
|
2157
2185
|
const moved = await onRelocate(record.worktreePath).catch(() => null);
|
|
2158
2186
|
if (moved) applyRelocation(record);
|
package/src/index.tsx
CHANGED
|
@@ -70,7 +70,7 @@ if (result.kind === "help") {
|
|
|
70
70
|
process.stdout.write(`${worktreeStartMessage(started)}\n`);
|
|
71
71
|
process.chdir(started.worktree.path);
|
|
72
72
|
const { start } = await import("./main");
|
|
73
|
-
await start(result.options, {
|
|
73
|
+
await start(result.options, { worktreeStart: started });
|
|
74
74
|
} catch (error) {
|
|
75
75
|
process.stderr.write(formatCliError(errorMessage(error)));
|
|
76
76
|
process.exitCode = 1;
|
package/src/main.tsx
CHANGED
|
@@ -69,14 +69,16 @@ import {
|
|
|
69
69
|
ManagedShellLifecycleController,
|
|
70
70
|
lifecycleEventFromSnapshot,
|
|
71
71
|
} from "./shells/lifecycle";
|
|
72
|
+
import { initializeWorktreeLaunchRelocation, type RelocationRecord } from "./relocation";
|
|
73
|
+
import type { WorktreeStart } from "./worktree-start";
|
|
72
74
|
|
|
73
75
|
/**
|
|
74
76
|
* Process-local launch context. These are facts about how this process was
|
|
75
77
|
* started, never user settings, so nothing here reaches pum.json.
|
|
76
78
|
*/
|
|
77
79
|
export type LaunchContext = {
|
|
78
|
-
/**
|
|
79
|
-
|
|
80
|
+
/** Generated checkout and source repository from a `pum worktree` launch. */
|
|
81
|
+
worktreeStart?: WorktreeStart;
|
|
80
82
|
};
|
|
81
83
|
|
|
82
84
|
export async function start(
|
|
@@ -98,7 +100,7 @@ export async function start(
|
|
|
98
100
|
// learn about it.
|
|
99
101
|
const forcedCheckPaths = [
|
|
100
102
|
...(outerSandbox ? outerSandboxAdditionalRoots(outerSandbox, process.cwd()) : []),
|
|
101
|
-
...(context.
|
|
103
|
+
...(context.worktreeStart ? [context.worktreeStart.sourceRoot] : []),
|
|
102
104
|
];
|
|
103
105
|
const sandboxController = new SandboxController({
|
|
104
106
|
mode: outerSandbox ? "off" : settings.sandboxMode ?? "auto",
|
|
@@ -314,6 +316,15 @@ export async function start(
|
|
|
314
316
|
statsManager.bindMainSession(sessionRuntime.session);
|
|
315
317
|
if (sessionRuntime.modelFallbackMessage) console.error(sessionRuntime.modelFallbackMessage);
|
|
316
318
|
|
|
319
|
+
// `pum worktree` creates the checkout before the session exists. Persist it
|
|
320
|
+
// now so return and resume use the ordinary relocation path.
|
|
321
|
+
const initialRelocation: RelocationRecord | undefined = context.worktreeStart
|
|
322
|
+
? initializeWorktreeLaunchRelocation(
|
|
323
|
+
sessionRuntime.session.sessionManager.getSessionFile(),
|
|
324
|
+
context.worktreeStart,
|
|
325
|
+
)
|
|
326
|
+
: undefined;
|
|
327
|
+
|
|
317
328
|
const renderer = await createCliRenderer({ exitOnCtrlC: false });
|
|
318
329
|
const terminalTitle = new TerminalTitleController((title) => renderer.setTerminalTitle(title));
|
|
319
330
|
const selectionClipboard = installSelectionClipboard(renderer);
|
|
@@ -431,6 +442,7 @@ export async function start(
|
|
|
431
442
|
}}
|
|
432
443
|
forcedSandboxMode={outerSandbox ? "off" : undefined}
|
|
433
444
|
forcedCheckPaths={forcedCheckPaths}
|
|
445
|
+
initialRelocation={initialRelocation}
|
|
434
446
|
sandboxWarningSource={sandboxController}
|
|
435
447
|
loginRequired={loginRequired}
|
|
436
448
|
triggerManager={triggerManager}
|
package/src/relocation.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { companionFileFor, readCompanion, writeCompanion } from "./session-companion";
|
|
2
3
|
import { isPathInsideOrSame, pathIdentity } from "./platform";
|
|
4
|
+
import type { WorktreeStart } from "./worktree-start";
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Where a relocated session is currently running, and what move it still owes.
|
|
@@ -76,6 +78,34 @@ export function saveRelocation(
|
|
|
76
78
|
writeCompanion(sessionFile, RELOCATION_SUFFIX, record);
|
|
77
79
|
}
|
|
78
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Persist the generated worktree that a `pum worktree` CLI launch entered.
|
|
83
|
+
*
|
|
84
|
+
* The CLI creates the checkout before the session exists. This bridges that
|
|
85
|
+
* launch fact into the same companion record used by in-session start/return.
|
|
86
|
+
*/
|
|
87
|
+
export function initializeWorktreeLaunchRelocation(
|
|
88
|
+
sessionFile: string | undefined,
|
|
89
|
+
start: WorktreeStart,
|
|
90
|
+
now = Date.now(),
|
|
91
|
+
): RelocationRecord {
|
|
92
|
+
const record: RelocationRecord = {
|
|
93
|
+
id: `reloc-${randomUUID().slice(0, 8)}`,
|
|
94
|
+
generation: 1,
|
|
95
|
+
sourceRoot: start.sourceRoot,
|
|
96
|
+
worktreePath: start.worktree.path,
|
|
97
|
+
name: start.worktree.name,
|
|
98
|
+
branch: start.worktree.branch,
|
|
99
|
+
baseBranch: start.worktree.baseBranch,
|
|
100
|
+
baseCommit: start.worktree.baseCommit,
|
|
101
|
+
location: "worktree",
|
|
102
|
+
createdAt: now,
|
|
103
|
+
updatedAt: now,
|
|
104
|
+
};
|
|
105
|
+
saveRelocation(sessionFile, record);
|
|
106
|
+
return record;
|
|
107
|
+
}
|
|
108
|
+
|
|
79
109
|
export type RelocationGuardInput = {
|
|
80
110
|
relocation: RelocationRecord | null;
|
|
81
111
|
/** True while the main agent is working. A move mid-turn would change its roots. */
|