@yu_robotics/remote-cli 1.6.158 → 1.6.165

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.
Files changed (66) hide show
  1. package/README.md +10 -3
  2. package/dist/client/MessageHandler.d.ts +4 -5
  3. package/dist/client/MessageHandler.d.ts.map +1 -1
  4. package/dist/client/MessageHandler.js +102 -9
  5. package/dist/client/MessageHandler.js.map +1 -1
  6. package/dist/commands/config.d.ts.map +1 -1
  7. package/dist/commands/config.js +15 -0
  8. package/dist/commands/config.js.map +1 -1
  9. package/dist/commands/session.d.ts +17 -0
  10. package/dist/commands/session.d.ts.map +1 -0
  11. package/dist/commands/session.js +37 -0
  12. package/dist/commands/session.js.map +1 -0
  13. package/dist/delegation/GitCheckpoint.d.ts +1 -0
  14. package/dist/delegation/GitCheckpoint.d.ts.map +1 -1
  15. package/dist/delegation/GitCheckpoint.js +15 -4
  16. package/dist/delegation/GitCheckpoint.js.map +1 -1
  17. package/dist/executor/IExecutor.d.ts +21 -0
  18. package/dist/executor/IExecutor.d.ts.map +1 -1
  19. package/dist/executor/PiExecutor.d.ts +26 -1
  20. package/dist/executor/PiExecutor.d.ts.map +1 -1
  21. package/dist/executor/PiExecutor.js +256 -15
  22. package/dist/executor/PiExecutor.js.map +1 -1
  23. package/dist/index.js +19 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/maintenance/release-notes.json +1 -1
  26. package/dist/session/SessionCatalog.d.ts +76 -0
  27. package/dist/session/SessionCatalog.d.ts.map +1 -0
  28. package/dist/session/SessionCatalog.js +197 -0
  29. package/dist/session/SessionCatalog.js.map +1 -0
  30. package/dist/session/SessionControl.d.ts +56 -0
  31. package/dist/session/SessionControl.d.ts.map +1 -0
  32. package/dist/session/SessionControl.js +262 -0
  33. package/dist/session/SessionControl.js.map +1 -0
  34. package/dist/session/SessionLocalInfo.d.ts +48 -0
  35. package/dist/session/SessionLocalInfo.d.ts.map +1 -0
  36. package/dist/session/SessionLocalInfo.js +145 -0
  37. package/dist/session/SessionLocalInfo.js.map +1 -0
  38. package/dist/session/SessionMenu.d.ts +102 -0
  39. package/dist/session/SessionMenu.d.ts.map +1 -0
  40. package/dist/session/SessionMenu.js +144 -0
  41. package/dist/session/SessionMenu.js.map +1 -0
  42. package/dist/session/SessionResumeService.d.ts +38 -0
  43. package/dist/session/SessionResumeService.d.ts.map +1 -0
  44. package/dist/session/SessionResumeService.js +147 -0
  45. package/dist/session/SessionResumeService.js.map +1 -0
  46. package/dist/session/adapters/ClaudeSessionCatalog.d.ts +10 -0
  47. package/dist/session/adapters/ClaudeSessionCatalog.d.ts.map +1 -0
  48. package/dist/session/adapters/ClaudeSessionCatalog.js +156 -0
  49. package/dist/session/adapters/ClaudeSessionCatalog.js.map +1 -0
  50. package/dist/session/adapters/CodexSessionCatalog.d.ts +9 -0
  51. package/dist/session/adapters/CodexSessionCatalog.d.ts.map +1 -0
  52. package/dist/session/adapters/CodexSessionCatalog.js +214 -0
  53. package/dist/session/adapters/CodexSessionCatalog.js.map +1 -0
  54. package/dist/session/adapters/PiSessionCatalog.d.ts +52 -0
  55. package/dist/session/adapters/PiSessionCatalog.d.ts.map +1 -0
  56. package/dist/session/adapters/PiSessionCatalog.js +252 -0
  57. package/dist/session/adapters/PiSessionCatalog.js.map +1 -0
  58. package/dist/session/adapters/index.d.ts +13 -0
  59. package/dist/session/adapters/index.d.ts.map +1 -0
  60. package/dist/session/adapters/index.js +17 -0
  61. package/dist/session/adapters/index.js.map +1 -0
  62. package/dist/types/config.d.ts +11 -0
  63. package/dist/types/config.d.ts.map +1 -1
  64. package/dist/types/config.js +3 -0
  65. package/dist/types/config.js.map +1 -1
  66. package/package.json +1 -1
@@ -37,12 +37,23 @@ exports.PiExecutor = void 0;
37
37
  const fs = __importStar(require("fs"));
38
38
  const os = __importStar(require("os"));
39
39
  const path = __importStar(require("path"));
40
+ const crypto_1 = require("crypto");
40
41
  const ExecutionMetadata_1 = require("./ExecutionMetadata");
41
42
  const PiClient_1 = require("./pi/PiClient");
42
43
  const contract_1 = require("../delegation/contract");
43
44
  const WorkingDirectory_1 = require("../utils/WorkingDirectory");
44
45
  const Activity_1 = require("./Activity");
45
46
  const PiTypes_1 = require("./pi/PiTypes");
47
+ const MISSING_ADOPTED_ERROR = 'The connected Pi session is no longer available on this computer. Send /session detach to start a fresh conversation, or /resume to connect another session.';
48
+ function sameFilePath(left, right) {
49
+ const canonical = (value) => { try {
50
+ return fs.realpathSync.native(value);
51
+ }
52
+ catch {
53
+ return path.resolve(value);
54
+ } };
55
+ return canonical(left) === canonical(right);
56
+ }
46
57
  const DEFAULT_TURN_TIMEOUT_MS = 10 * 60 * 1000;
47
58
  const COMPACT_TIMEOUT_MS = 10 * 60 * 1000;
48
59
  /** Grace period to distinguish immediate slash/extension commands from a real agent turn. */
@@ -176,6 +187,15 @@ class PiExecutor {
176
187
  unsubscribe = null;
177
188
  sessionId = null;
178
189
  sessionFile = null;
190
+ /** Fresh foreground identity, distinct from the stable remote thread. */
191
+ freshSessionId = (0, crypto_1.randomUUID)();
192
+ /** Adopted native history is user-owned: never deleted, never renamed. */
193
+ adopted = false;
194
+ nativeSessionMutation = false;
195
+ nativeSessionMutationCancelled = false;
196
+ nativeSessionMutationDone = null;
197
+ nativeSessionCleanup = 0;
198
+ preparingTurn = null;
179
199
  activeTurn = null;
180
200
  activityTracker = new Activity_1.ActivityTracker();
181
201
  pendingUi = null;
@@ -185,7 +205,7 @@ class PiExecutor {
185
205
  async configureDelegation(connection) {
186
206
  if ((0, contract_1.sameConnection)(this.delegation, connection))
187
207
  return;
188
- if (this.activeTurn)
208
+ if (this.isBusy())
189
209
  throw new Error('Cannot change delegation while Pi is busy');
190
210
  await this.recycleClient();
191
211
  this.delegation = connection;
@@ -227,24 +247,42 @@ class PiExecutor {
227
247
  async execute(prompt, options = {}) {
228
248
  if (this.destroyed)
229
249
  throw new Error('Executor has been destroyed');
230
- if (this.activeTurn) {
250
+ if (this.isBusy()) {
231
251
  return { success: false, error: 'Pi thread is already running a command' };
232
252
  }
233
253
  const trimmed = prompt.trim();
234
254
  if (trimmed === '/skills' || trimmed === '/skill') {
235
255
  return this.listSkills();
236
256
  }
237
- if (trimmed.startsWith('/')) {
238
- const validationError = await this.validateSlashCommand(trimmed);
239
- if (validationError)
240
- return { success: false, error: validationError };
241
- }
257
+ const preparation = { cancelled: false };
258
+ this.preparingTurn = preparation;
242
259
  try {
260
+ // Each native Pi process caches a branch. Reload adopted history before
261
+ // every remote turn; the user must keep other writers idle.
262
+ if (this.adopted)
263
+ await this.recycleClient();
264
+ if (preparation.cancelled || this.destroyed)
265
+ return { success: false, error: 'Pi command cancelled before startup' };
243
266
  await this.ensureClient();
267
+ if (preparation.cancelled || this.destroyed) {
268
+ await this.recycleClient();
269
+ return { success: false, error: 'Pi command cancelled before dispatch' };
270
+ }
271
+ if (trimmed.startsWith('/')) {
272
+ const validationError = await this.validateSlashCommand(trimmed);
273
+ if (validationError)
274
+ return { success: false, error: validationError };
275
+ }
276
+ if (preparation.cancelled || this.destroyed)
277
+ return { success: false, error: 'Pi command cancelled before dispatch' };
244
278
  }
245
279
  catch (error) {
246
280
  return { success: false, error: this.friendlyError(error) };
247
281
  }
282
+ finally {
283
+ if (this.preparingTurn === preparation)
284
+ this.preparingTurn = null;
285
+ }
248
286
  return new Promise((resolve) => {
249
287
  const active = {
250
288
  options,
@@ -325,7 +363,7 @@ class PiExecutor {
325
363
  return this.currentWorkingDirectory;
326
364
  }
327
365
  isBusy() {
328
- return this.activeTurn !== null;
366
+ return this.activeTurn !== null || this.preparingTurn !== null || this.nativeSessionMutation || this.nativeSessionCleanup > 0;
329
367
  }
330
368
  async setWorkingDirectory(targetPath) {
331
369
  const resolved = this.directoryGuard.resolveWorkingDirectory(targetPath, this.currentWorkingDirectory);
@@ -343,6 +381,10 @@ class PiExecutor {
343
381
  resetContext() {
344
382
  if (this.destroyed)
345
383
  return;
384
+ if (this.nativeSessionMutation)
385
+ throw new Error('Cannot clear Pi context while a session connection is changing');
386
+ if (this.preparingTurn)
387
+ this.preparingTurn.cancelled = true;
346
388
  this.cancelPendingUi(true);
347
389
  if (this.activeTurn) {
348
390
  this.completeActive({
@@ -355,6 +397,17 @@ class PiExecutor {
355
397
  this.replaceClient();
356
398
  }
357
399
  async abort() {
400
+ const mutation = this.nativeSessionMutationDone;
401
+ if (mutation) {
402
+ this.nativeSessionMutationCancelled = true;
403
+ await this.stopNativeSessionMutation(mutation);
404
+ return true;
405
+ }
406
+ if (this.preparingTurn) {
407
+ this.preparingTurn.cancelled = true;
408
+ await this.recycleClient();
409
+ return true;
410
+ }
358
411
  if (!this.client.isRunning())
359
412
  return false;
360
413
  const active = this.activeTurn;
@@ -387,13 +440,36 @@ class PiExecutor {
387
440
  }
388
441
  async destroy() {
389
442
  this.destroyed = true;
443
+ this.nativeSessionMutationCancelled = true;
444
+ if (this.preparingTurn)
445
+ this.preparingTurn.cancelled = true;
390
446
  this.cancelPendingUi(true);
391
447
  if (this.activeTurn) {
392
448
  this.completeActive({ success: false, error: 'Executor destroyed' });
393
449
  }
394
450
  this.unsubscribe?.();
395
451
  this.unsubscribe = null;
396
- await this.recycleClient();
452
+ const mutation = this.nativeSessionMutationDone;
453
+ if (mutation)
454
+ await this.stopNativeSessionMutation(mutation);
455
+ else
456
+ await this.recycleClient();
457
+ }
458
+ /** Abort native context changes without RPC requests that could restart a stopped client. */
459
+ async stopNativeSessionMutation(mutation) {
460
+ this.nativeSessionCleanup += 1;
461
+ try {
462
+ // Interrupt startup first, then await rollback before the final exit check.
463
+ try {
464
+ await this.recycleClient();
465
+ }
466
+ catch { /* confirmed cleanup is required below */ }
467
+ await mutation;
468
+ await this.recycleClient();
469
+ }
470
+ finally {
471
+ this.nativeSessionCleanup -= 1;
472
+ }
397
473
  }
398
474
  /**
399
475
  * `recycleClient` waits for the Pi transport's stop operation. Check every
@@ -448,7 +524,7 @@ class PiExecutor {
448
524
  return this.compactWhenFull(onStream);
449
525
  }
450
526
  async compactWhenFull(onStream) {
451
- if (this.activeTurn) {
527
+ if (this.isBusy()) {
452
528
  return { success: false, error: 'Cannot compact while a Pi command is running' };
453
529
  }
454
530
  try {
@@ -655,9 +731,11 @@ class PiExecutor {
655
731
  async deleteThreadData(threadId) {
656
732
  const pointerPath = path.join(path.dirname(this.sessionFilePath), `${threadId}.json`);
657
733
  let sessionFile = this.sessionFile;
734
+ let adopted = threadId === this.threadId && this.adopted;
658
735
  try {
659
736
  const stored = JSON.parse(fs.readFileSync(pointerPath, 'utf8'));
660
737
  sessionFile = stored.sessionFile ?? sessionFile;
738
+ adopted = adopted || stored.adopted === true;
661
739
  }
662
740
  catch {
663
741
  // Pointer may already be gone.
@@ -666,7 +744,7 @@ class PiExecutor {
666
744
  fs.unlinkSync(pointerPath);
667
745
  }
668
746
  catch { /* ignore */ }
669
- if (sessionFile) {
747
+ if (sessionFile && !adopted && !this.isForegroundSession(threadId)) {
670
748
  try {
671
749
  fs.unlinkSync(sessionFile);
672
750
  }
@@ -675,6 +753,7 @@ class PiExecutor {
675
753
  if (threadId === this.threadId) {
676
754
  this.sessionId = null;
677
755
  this.sessionFile = null;
756
+ this.adopted = false;
678
757
  }
679
758
  }
680
759
  async listSkills() {
@@ -715,6 +794,8 @@ class PiExecutor {
715
794
  async ensureClient() {
716
795
  if (this.destroyed)
717
796
  throw new Error('Executor has been destroyed');
797
+ if (this.adopted && (!this.sessionFile || !fs.existsSync(this.sessionFile)))
798
+ throw new Error(MISSING_ADOPTED_ERROR);
718
799
  (0, WorkingDirectory_1.assertWorkingDirectoryExists)(this.currentWorkingDirectory);
719
800
  if (this.retiredClients.size > 0) {
720
801
  await Promise.all([...this.retiredClients].map(client => this.stopRetiredClient(client)));
@@ -727,9 +808,150 @@ class PiExecutor {
727
808
  this.reportedEffort = undefined;
728
809
  this.client.updateLaunch?.(this.buildLaunch());
729
810
  await this.client.start();
730
- await this.refreshSessionPointer();
811
+ if (!this.adopted) {
812
+ await this.refreshSessionPointer();
813
+ return;
814
+ }
815
+ // Adopted sessions never follow a silently substituted identity after a restart.
816
+ const mismatch = await this.checkAdoptedIdentity({ sessionId: this.sessionId, sessionFile: this.sessionFile });
817
+ if (mismatch) {
818
+ await this.client.stop().catch(() => undefined);
819
+ throw new Error(`${mismatch} ${MISSING_ADOPTED_ERROR}`);
820
+ }
731
821
  }
732
822
  }
823
+ /**
824
+ * Connect this thread to an existing native Pi session file. Verification uses
825
+ * RPC `get_state` only: no prompt is sent. Pi may append ordinary metadata
826
+ * entries (for example a thinking-level record) but keeps prior history intact.
827
+ */
828
+ async adoptNativeSession(target) {
829
+ const fail = (error) => ({ success: false, error });
830
+ if (this.destroyed)
831
+ return fail('Executor has been destroyed.');
832
+ if (this.isBusy())
833
+ return fail('Wait for the running Pi task to finish before connecting a session.');
834
+ if (!fs.existsSync(target.sessionFile))
835
+ return fail('The selected Pi session is no longer available. No session was changed.');
836
+ return this.mutateNativeSession(() => this.adoptIdleNativeSession(target));
837
+ }
838
+ async mutateNativeSession(operation) {
839
+ this.nativeSessionMutation = true;
840
+ this.nativeSessionMutationCancelled = false;
841
+ let finish;
842
+ this.nativeSessionMutationDone = new Promise(resolve => { finish = resolve; });
843
+ try {
844
+ return await operation();
845
+ }
846
+ finally {
847
+ this.nativeSessionMutation = false;
848
+ this.nativeSessionMutationDone = null;
849
+ finish();
850
+ }
851
+ }
852
+ async adoptIdleNativeSession(target) {
853
+ const fail = (error) => ({ success: false, error });
854
+ const previous = { sessionId: this.sessionId, sessionFile: this.sessionFile, adopted: this.adopted,
855
+ reportedModel: this.reportedModel, reportedEffort: this.reportedEffort };
856
+ try {
857
+ await this.recycleClient();
858
+ }
859
+ catch (error) {
860
+ return fail(`Could not confirm that the current Pi process stopped (${this.friendlyError(error)}). No session was changed.`);
861
+ }
862
+ if (this.destroyed || this.nativeSessionMutationCancelled)
863
+ return fail('Pi session connection was cancelled. No session was connected.');
864
+ this.sessionId = target.sessionId;
865
+ this.sessionFile = target.sessionFile;
866
+ this.adopted = true;
867
+ let error;
868
+ try {
869
+ (0, WorkingDirectory_1.assertWorkingDirectoryExists)(this.currentWorkingDirectory);
870
+ this.reportedModel = undefined;
871
+ this.reportedEffort = undefined;
872
+ this.client.updateLaunch?.(this.buildLaunch());
873
+ await this.client.start();
874
+ // PiClient.request() can start a process; never call it after cancellation.
875
+ if (!this.destroyed && !this.nativeSessionMutationCancelled)
876
+ error = await this.checkAdoptedIdentity(target);
877
+ if (this.destroyed || this.nativeSessionMutationCancelled)
878
+ error = 'Pi session connection was cancelled.';
879
+ if (!error && !this.savePointer())
880
+ error = 'Could not persist the Pi session connection.';
881
+ }
882
+ catch (startError) {
883
+ error = this.friendlyError(startError);
884
+ }
885
+ if (!error) {
886
+ return { success: true };
887
+ }
888
+ let stopped = true;
889
+ try {
890
+ await this.client.stop();
891
+ }
892
+ catch {
893
+ stopped = false;
894
+ }
895
+ Object.assign(this, previous);
896
+ // An unconfirmed stop is retired and retried before the previous session starts again.
897
+ if (stopped)
898
+ this.client.updateLaunch?.(this.buildLaunch());
899
+ else
900
+ this.replaceClient();
901
+ return { success: false, error: `${error} The previous conversation is kept.${stopped ? '' : ' Selected Pi process exit could not be confirmed; the session remains reserved until recovery.'}`,
902
+ ...(!stopped ? { recoveryRequired: true } : {}) };
903
+ }
904
+ /** Forget the adopted session without deleting it; the next turn starts a fresh conversation. */
905
+ async detachNativeSession() {
906
+ if (this.destroyed)
907
+ return { success: false, error: 'Executor has been destroyed.' };
908
+ if (!this.adopted)
909
+ return { success: false, error: 'This thread is not connected to a native Pi session.' };
910
+ if (this.isBusy())
911
+ return { success: false, error: 'Wait for the running Pi task to finish (or send /abort) before detaching the session.' };
912
+ return this.mutateNativeSession(() => this.detachIdleNativeSession());
913
+ }
914
+ async detachIdleNativeSession() {
915
+ const previous = { sessionId: this.sessionId, sessionFile: this.sessionFile, adopted: this.adopted };
916
+ try {
917
+ await this.recycleClient();
918
+ }
919
+ catch (error) {
920
+ return { success: false, error: `Could not confirm that the Pi process stopped (${this.friendlyError(error)}). The session is still connected; try again.` };
921
+ }
922
+ if (this.destroyed || this.nativeSessionMutationCancelled)
923
+ return { success: false, error: 'Pi session detach was cancelled. The session is still connected.' };
924
+ this.adopted = false;
925
+ this.sessionFile = null;
926
+ this.sessionId = (0, crypto_1.randomUUID)();
927
+ if (!this.savePointer()) {
928
+ Object.assign(this, previous);
929
+ this.client.updateLaunch?.(this.buildLaunch());
930
+ return { success: false, error: 'Could not persist the Pi session detach. The session is still connected; try again.' };
931
+ }
932
+ this.client.updateLaunch?.(this.buildLaunch());
933
+ return { success: true };
934
+ }
935
+ /** Returns an error description when Pi did not load exactly the expected session. */
936
+ async checkAdoptedIdentity(expected) {
937
+ const response = await this.client.request({ type: 'get_state' });
938
+ const data = response.success ? response.data : undefined;
939
+ if (!data)
940
+ return 'Pi did not report its session state.';
941
+ if (data.sessionId !== expected.sessionId || typeof data.sessionFile !== 'string'
942
+ || !sameFilePath(data.sessionFile, expected.sessionFile)) {
943
+ return 'Pi opened a different session than the one selected.';
944
+ }
945
+ if (data.isStreaming === true)
946
+ return 'Pi reported that the session is already running a turn.';
947
+ const model = data.model;
948
+ if (typeof model?.id === 'string' && model.id.trim()) {
949
+ this.reportedModel = typeof model.provider === 'string' && model.provider ? `${model.provider}/${model.id}` : model.id;
950
+ }
951
+ if (typeof data.thinkingLevel === 'string' && data.thinkingLevel.trim())
952
+ this.reportedEffort = data.thinkingLevel;
953
+ return undefined;
954
+ }
733
955
  async recycleClient() {
734
956
  await Promise.all([
735
957
  this.client.stop(),
@@ -754,10 +976,15 @@ class PiExecutor {
754
976
  console.warn('[PiExecutor] Failed to stop previous Pi RPC process', error);
755
977
  });
756
978
  }
979
+ isForegroundSession(threadId = this.threadId) {
980
+ return !this.delegationWorker && !/^(?:remote-cli-)?(?:delegate-lane-|settings-meta-)/.test(threadId ?? '');
981
+ }
757
982
  forgetSession() {
758
983
  const sessionFile = this.sessionFile;
984
+ const preserveHistory = this.adopted || this.isForegroundSession();
759
985
  this.clearPointer();
760
- if (sessionFile) {
986
+ // Reset changes the foreground association, never deletes resumable history.
987
+ if (sessionFile && !preserveHistory) {
761
988
  try {
762
989
  fs.unlinkSync(sessionFile);
763
990
  }
@@ -766,14 +993,21 @@ class PiExecutor {
766
993
  }
767
994
  buildLaunch() {
768
995
  const pointerFile = this.sessionFile && fs.existsSync(this.sessionFile) ? this.sessionFile : undefined;
996
+ const sessionId = this.sessionId ?? (this.isForegroundSession() ? this.freshSessionId : this.threadId);
769
997
  return {
770
998
  command: this.piCommand,
771
999
  cwd: this.currentWorkingDirectory,
772
1000
  approveProject: this.autoApprove,
773
1001
  sessionDir: this.sessionStoreDir,
774
- sessionId: this.sessionId ?? this.threadId,
1002
+ sessionId,
775
1003
  sessionFile: pointerFile,
776
- sessionName: this.threadId ? `remote-cli-${this.threadId}` : undefined,
1004
+ // Name fresh foreground sessions by native identity, not their reusable thread.
1005
+ // Never rename a persisted file on reopen, including locally assigned names.
1006
+ sessionName: this.threadId && !this.adopted
1007
+ ? this.isForegroundSession()
1008
+ ? !pointerFile ? `remote-cli-${sessionId}` : undefined
1009
+ : `remote-cli-${this.threadId}`
1010
+ : undefined,
777
1011
  provider: this.provider,
778
1012
  model: this.model,
779
1013
  thinking: this.effort,
@@ -1017,6 +1251,8 @@ class PiExecutor {
1017
1251
  this.sessionId = stored.id;
1018
1252
  if (stored.sessionFile)
1019
1253
  this.sessionFile = stored.sessionFile;
1254
+ this.adopted = stored.adopted === true;
1255
+ // Legacy released flags no longer gate adopted sessions.
1020
1256
  }
1021
1257
  catch {
1022
1258
  // First run for this thread.
@@ -1027,11 +1263,13 @@ class PiExecutor {
1027
1263
  id: this.sessionId ?? undefined,
1028
1264
  sessionFile: this.sessionFile ?? undefined,
1029
1265
  cwd: this.currentWorkingDirectory,
1266
+ ...(this.adopted ? { adopted: true } : {}),
1030
1267
  };
1031
1268
  const temporaryPath = `${this.sessionFilePath}.${process.pid}.tmp`;
1032
1269
  try {
1033
1270
  fs.writeFileSync(temporaryPath, JSON.stringify(pointer), { encoding: 'utf8', mode: 0o600 });
1034
1271
  fs.renameSync(temporaryPath, this.sessionFilePath);
1272
+ return true;
1035
1273
  }
1036
1274
  catch (error) {
1037
1275
  try {
@@ -1039,11 +1277,14 @@ class PiExecutor {
1039
1277
  }
1040
1278
  catch { /* ignore */ }
1041
1279
  console.warn('[PiExecutor] Failed to save Pi session pointer', error);
1280
+ return false;
1042
1281
  }
1043
1282
  }
1044
1283
  clearPointer() {
1045
1284
  this.sessionId = null;
1046
1285
  this.sessionFile = null;
1286
+ this.adopted = false;
1287
+ this.freshSessionId = (0, crypto_1.randomUUID)();
1047
1288
  try {
1048
1289
  fs.unlinkSync(this.sessionFilePath);
1049
1290
  }