@agfpd/iapeer 0.4.16 → 0.4.17

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
@@ -10,6 +10,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [0.4.17] - 2026-06-21
14
+
15
+ ### Added
16
+
17
+ - `iapeer rename <old> <new>` — first-class peer-identity rename (parity with remove/create).
18
+ Renames the personality in **both** the registry and the per-cwd profile atomically and
19
+ **keeps the cwd**, so the claude transcript history (keyed by `realpath(cwd)`) survives.
20
+ Refuses a LIVE peer unless `--force` (a running session carries the old identity in its
21
+ env), and purges the old identity's lifecycle markers. Memory re-key (the personality-keyed
22
+ operativka folder + author/index attribution) is the memory provider's separate step;
23
+ native-memory is cwd-keyed and unaffected.
24
+
13
25
  ## [0.4.16] - 2026-06-21
14
26
 
15
27
  ### Fixed
@@ -245,7 +257,8 @@ Initial public release — the foundation core of the iapeer multi-agent ecosyst
245
257
  - **Heterogeneous peers** — AI agents (Claude, Codex), humans (Telegram), and services
246
258
  (timer = cron, watcher = event) are all first-class and addressed the same way.
247
259
 
248
- [Unreleased]: https://github.com/agfpd/iapeer/compare/v0.4.16...HEAD
260
+ [Unreleased]: https://github.com/agfpd/iapeer/compare/v0.4.17...HEAD
261
+ [0.4.17]: https://github.com/agfpd/iapeer/compare/v0.4.16...v0.4.17
249
262
  [0.4.16]: https://github.com/agfpd/iapeer/compare/v0.4.15...v0.4.16
250
263
  [0.4.15]: https://github.com/agfpd/iapeer/compare/v0.4.14...v0.4.15
251
264
  [0.4.14]: https://github.com/agfpd/iapeer/compare/v0.4.13...v0.4.14
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agfpd/iapeer",
3
- "version": "0.4.16",
3
+ "version": "0.4.17",
4
4
  "description": "Foundation core for the iapeer multi-agent ecosystem: identity, registry, storage, codec.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "agfpd",
package/src/cli/index.ts CHANGED
@@ -53,7 +53,7 @@ import { getAdapter } from '../launch/index.ts'
53
53
  import { hostRunDir } from '../launch/ptyHost.ts' // pty-only: attach via the supervisor client
54
54
  import { runSupervisorClient } from '../supervisor/client.ts' // @xterm-free attach client (both reattach port-deps baked in)
55
55
  import { cycleDaemon, isFoundationOwnedPlist, launchctlBootstrap, launchdLabel, launchdPlistPath } from '../launch/launchd.ts'
56
- import { readPeerProfile, resolveCallerIdentity, resolveIdentity, writePeerProfileAtomic } from '../identity/index.ts'
56
+ import { readPeerProfile, renamePeer, resolveCallerIdentity, resolveIdentity, writePeerProfileAtomic } from '../identity/index.ts'
57
57
  import { peerProfilePath } from '../storage/index.ts'
58
58
  import {
59
59
  isConformant,
@@ -827,6 +827,61 @@ export async function removePeerCli(
827
827
  }
828
828
  }
829
829
 
830
+ // ─────────────────────────────────────────────────────────────────────────────
831
+ // rename — first-class peer-identity rename (parity with remove/create). Wraps
832
+ // renamePeer (registry + per-cwd profile, atomic, inside the lock) and KEEPS the
833
+ // cwd, so the claude transcript history (slug = realpath(cwd)) survives the rename.
834
+ // ─────────────────────────────────────────────────────────────────────────────
835
+
836
+ export interface RenameOutcome {
837
+ oldPersonality: string
838
+ newPersonality: string
839
+ action: 'renamed' | 'absent' | 'target-exists' | 'refused-live'
840
+ reason?: string
841
+ cwd?: string
842
+ purgedState?: string[]
843
+ }
844
+
845
+ /**
846
+ * Rename a peer's personality through renamePeer (registry + per-cwd profile, atomic;
847
+ * cwd KEPT — the transcript history keyed by slug=realpath(cwd) survives). Refuses a LIVE
848
+ * peer unless --force: a running session carries the OLD identity in its env
849
+ * (PEER_PERSONALITY, set at launch), so renaming under it desyncs the registry from the
850
+ * live process — stop it first, it respawns under the new identity. Purges the OLD
851
+ * identity-keyed lifecycle markers (remove-parity) so a future namesake can't inherit
852
+ * them. Does NOT move memory: the operativka folder + author/index attribution are
853
+ * personality-keyed (the memory provider re-keys them separately); native-memory is
854
+ * cwd-keyed and rides the kept cwd unchanged.
855
+ */
856
+ export async function renamePeerCli(
857
+ oldPersonality: string,
858
+ newPersonality: string,
859
+ opts: CliEnvOptions & { force?: boolean } = {},
860
+ ): Promise<RenameOutcome> {
861
+ const env = opts.env ?? process.env
862
+ const index = readPeersIndex({ env })
863
+ const peer = findPeer(index, oldPersonality)
864
+ if (!peer) return { oldPersonality, newPersonality, action: 'absent' }
865
+ if (findPeer(index, newPersonality)) {
866
+ return { oldPersonality, newPersonality, action: 'target-exists', reason: `"${newPersonality}" already exists` }
867
+ }
868
+ const cfg = loadLifecycleConfig(env)
869
+ if (!opts.force) {
870
+ const liveRt = peer.runtimes.find(rt => isPeerLive(rt, oldPersonality, cfg.sockDir))
871
+ if (liveRt) {
872
+ return {
873
+ oldPersonality,
874
+ newPersonality,
875
+ action: 'refused-live',
876
+ reason: `"${oldPersonality}" is LIVE on ${liveRt} — its running session carries the old identity in its env; stop it first (\`iapeer stop ${oldPersonality}\`) or pass --force`,
877
+ }
878
+ }
879
+ }
880
+ await renamePeer(oldPersonality, newPersonality, { env })
881
+ const purgedState = peer.runtimes.flatMap(rt => purgeIdentityState(cfg, buildProcessAddress(rt, oldPersonality)))
882
+ return { oldPersonality, newPersonality, action: 'renamed', cwd: peer.cwd, purgedState }
883
+ }
884
+
830
885
  // ─────────────────────────────────────────────────────────────────────────────
831
886
  // send — manual IAP send fallback (contract Примитивы §send). Goes through the
832
887
  // same router path as send_to_peer (resolve → deliver / wake), in-process so it
@@ -999,6 +1054,7 @@ const VERBS: ReadonlyArray<{ sig: string; desc: string }> = [
999
1054
  desc: 'LAZY soft-reload: agentic peer comes up FRESH (re-reads doctrine) on its NEXT natural wake — no kill, no burst-wake; non-agentic runtimes skipped. --all = whole fleet. (eager: `new`/self-fresh)',
1000
1055
  },
1001
1056
  { sig: 'remove <peer> [--force]', desc: 'delete a peer\'s registry record (locked writer); refuses a LIVE peer unless --force' },
1057
+ { sig: 'rename <old> <new> [--force]', desc: 'rename a peer\'s personality (registry + per-cwd profile, atomic; cwd/transcript-history kept); refuses a LIVE peer unless --force. Memory re-key is the provider\'s separate step.' },
1002
1058
  {
1003
1059
  sig: 'send <target> (--message <text> | --message-file <f|->) [--from <id>] [--attachment <p>]… [--topic <t>]',
1004
1060
  desc: 'manual IAP send (fallback)',
@@ -1702,6 +1758,25 @@ export async function runCli(argv: string[], env: NodeJS.ProcessEnv = process.en
1702
1758
  else errOut(`remove: ${o.reason}\n`)
1703
1759
  return o.action === 'refused-live' ? 1 : 0
1704
1760
  }
1761
+ case 'rename': {
1762
+ // Rename a peer's personality (registry + per-cwd profile, atomic; cwd KEPT so
1763
+ // the transcript history survives). Refuses a LIVE peer unless --force. Memory
1764
+ // (operativka folder + author/index) is personality-keyed and re-keyed separately
1765
+ // by the provider — say so, or it reads as a silent data loss.
1766
+ if (!positionals[0] || !positionals[1]) {
1767
+ return argErr(errOut, 'rename needs old and new names — usage: iapeer rename <old> <new> [--force]')
1768
+ }
1769
+ const o = await renamePeerCli(positionals[0], positionals[1], { force: flags.force === true, env })
1770
+ if (o.action === 'renamed') {
1771
+ out(`renamed "${o.oldPersonality}" → "${o.newPersonality}" (registry + per-cwd profile; cwd kept: ${o.cwd})\n`)
1772
+ if (o.purgedState?.length) out(`lifecycle state purged (old identity): ${o.purgedState.join(', ')}\n`)
1773
+ out(
1774
+ `NOTE: memory is NOT moved by rename — re-key the operativka folder + author/index attribution under "${o.newPersonality}" via the memory provider (native-memory rides the kept cwd, unchanged). Wake "${o.newPersonality}" to verify identity + history.\n`,
1775
+ )
1776
+ } else if (o.action === 'absent') errOut(`rename: "${o.oldPersonality}" not registered\n`)
1777
+ else errOut(`rename: ${o.reason}\n`)
1778
+ return o.action === 'renamed' ? 0 : 1
1779
+ }
1705
1780
  case 'send': {
1706
1781
  // Message body from EITHER --message <text> OR --message-file <f> (f='-' →
1707
1782
  // stdin). The runtime packages (telegram/notifier) + monitor deliver via