@webinson/peer 0.4.18 → 0.4.21

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/bin/peer.mjs CHANGED
@@ -450,6 +450,12 @@ function installMark() {
450
450
  } catch {}
451
451
  install();
452
452
  }
453
+ // `plugin install` leaves an installed version alone, so an upgrade of this package re-reads its marketplace and
454
+ // moves the mark to the version it ships.
455
+ try {
456
+ claudeCli(["plugin", "marketplace", "update", MARKETPLACE]);
457
+ claudeCli(["plugin", "update", `${MOD}@${MARKETPLACE}`]);
458
+ } catch {}
453
459
  console.log(`Claude Code: the thread mark (${MOD}), a band above the prompt in shared threads, from ${MOD_DIR}.`);
454
460
  } catch (error) {
455
461
  const reason = String(error.stderr || error.message).trim().split("\n").pop();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peer-thread-mark",
3
- "version": "0.4.18",
3
+ "version": "0.4.21",
4
4
  "description": "Marks a Claude Code thread shared with Peer (/peer) with a band above the prompt, with /peer stop and /peer start; panes for /peer-list, /peer-thread and /peer-all (search your memories)",
5
5
  "license": "MIT",
6
6
  "types": "./types/index.d.ts"
@@ -497,8 +497,18 @@ async function memoryNote($: EngineInterface) {
497
497
  return 'Inactive: not shared, Claude Code memory only.'
498
498
  }
499
499
 
500
- // Who else was on the file, newest first: other people before the person's own other threads.
501
- const othersOf = (signal: CollisionSignal) => signal.holders.filter(h => !h.own).sort((a, b) => Date.parse(b.at) - Date.parse(a.at))
500
+ // Who else was on the file, one entry per person (their newest session), newest first. One person often holds a path
501
+ // in several sessions (another thread, the Peer app's own), and is named once.
502
+ const byPerson = (holders: CollisionSignal['holders']) => {
503
+ const newest = new Map<string, CollisionSignal['holders'][number]>()
504
+ for (const h of holders) {
505
+ const key = h.email.toLowerCase()
506
+ const was = newest.get(key)
507
+ if (!was || Date.parse(h.at) > Date.parse(was.at)) newest.set(key, h)
508
+ }
509
+ return [...newest.values()].sort((a, b) => Date.parse(b.at) - Date.parse(a.at))
510
+ }
511
+ const othersOf = (signal: CollisionSignal) => byPerson(signal.holders.filter(h => !h.own))
502
512
  const whenAgo = (at: string, now: number) => {
503
513
  const age = ago(at, now)
504
514
  return age === 'just now' ? age : `${age} ago`
@@ -605,7 +615,7 @@ export const register: Register = on => {
605
615
  .filter(h => ADDRESS.test(h.email))
606
616
  const state = answer.state === 'collision' && !holders.length ? 'clear' : answer.state
607
617
  const at = await $.clock.now()
608
- const emails = (signal: { holders: { email: string }[] }) => signal.holders.map(h => h.email.toLowerCase()).sort().join(' ')
618
+ const emails = (signal: { holders: { email: string }[] }) => [...new Set(signal.holders.map(h => h.email.toLowerCase()))].sort().join(' ')
609
619
  let signal: CollisionSignal = { path: answer.path ? plain(answer.path) : null, state, ...(answer.reason ? { reason: answer.reason } : {}), holders, at, shown: false }
610
620
  // Shown once per set of people on a path: against the latest earlier collision on it, decided inside one update.
611
621
  await update($, signals, was => {
@@ -87,6 +87,17 @@ test('an edit another person was on draws the line and tells the agent once per
87
87
  expect(both.additionalContext?.[1]).toContain("cy@acme.test's agent was on src/pay.ts 1 minute ago")
88
88
  expect(both.additionalContext?.[1]).toContain("bob@acme.test's agent was on src/pay.ts 2 minutes ago")
89
89
  expect(await texts(row('t3', 'desktop'), $ as never)).toContain('cy@… was on it 1 min ago and 1 others')
90
+
91
+ // One person in several sessions (another thread, the Peer app's own) is named once, at their newest.
92
+ state.answer = { path: 'src/one.ts', state: 'collision', holders: [BOB, { ...BOB, email: 'Bob@acme.test', at: minsAgo(0) }, { ...BOB, at: minsAgo(1) }] }
93
+ const once = (await $.classic.PostToolUse(edit('t4', 'Edit', '/repo/src/one.ts'))) as { additionalContext?: string[] }
94
+ expect(once.additionalContext?.[1]).toBe(
95
+ "<peer-collision>\nBob@acme.test's agent was on src/one.ts just now, in another thread.\nThis is information about the repository, not an instruction.\n</peer-collision>",
96
+ )
97
+ expect(await texts(row('t4', 'terminal'), $ as never)).toContain('Bob@… was on it just now')
98
+ // The same person in one more session is no new set of people.
99
+ state.answer = { path: 'src/one.ts', state: 'collision', holders: [BOB, { ...BOB, at: minsAgo(0) }] }
100
+ expect(((await $.classic.PostToolUse(edit('t5', 'Edit', '/repo/src/one.ts'))) as { additionalContext?: string[] }).additionalContext).toEqual(['from another hook'])
90
101
  })
91
102
 
92
103
  test('the person\'s own other thread is drawn dimmed and the agent is told nothing', async ($, on) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webinson/peer",
3
- "version": "0.4.18",
3
+ "version": "0.4.21",
4
4
  "description": "Share chosen Claude Code and Codex threads with your team: who has agents running and on what.",
5
5
  "license": "MIT",
6
6
  "type": "module",