@erclx/aitk 3.45.0 → 3.47.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.
@@ -1,3 +1,4 @@
1
+ import { basename } from 'node:path'
1
2
  import { $ } from 'bun'
2
3
  import { gitEnv } from '@/git-env'
3
4
  import {
@@ -186,3 +187,105 @@ export async function resolveSessions(
186
187
 
187
188
  return { kind: 'resolved', dir: registry.dir, confidence, sessions }
188
189
  }
190
+
191
+ /** The environment slice the caller's own identity is read from. */
192
+ export type Env = Record<string, string | undefined>
193
+
194
+ /** Why the caller's own row could not be named, rather than an empty result. */
195
+ export type SelfUnresolved = 'no-identity' | 'no-row'
196
+
197
+ /**
198
+ * What the environment offers about the session making the call.
199
+ *
200
+ * Both fields are candidates rather than a pair, since a client sets them
201
+ * independently and a caller can arrive carrying either one alone.
202
+ */
203
+ export interface SelfIdentity {
204
+ readonly sessionId: string | null
205
+ readonly pid: number | null
206
+ }
207
+
208
+ export type SelfReport =
209
+ | { readonly kind: 'self'; readonly session: ResolvedSession }
210
+ | {
211
+ readonly kind: 'unresolved'
212
+ readonly reason: SelfUnresolved
213
+ readonly identity: SelfIdentity
214
+ }
215
+
216
+ /**
217
+ * A positive integer, which is what separates a pid from a socket named for
218
+ * something else. Signal zero addresses the caller's own process group, so a
219
+ * zero would match a row rather than failing to.
220
+ */
221
+ function pidOf(value: string | undefined): number | null {
222
+ if (value === undefined) return null
223
+ const pid = Number(value.trim())
224
+ return Number.isInteger(pid) && pid > 0 ? pid : null
225
+ }
226
+
227
+ /**
228
+ * Reads whatever the environment states about the calling session.
229
+ *
230
+ * Three identifier namespaces are in play and only two of them join to a
231
+ * roster row. `CLAUDE_CODE_SESSION_ID` is the roster's own `sessionId` and is
232
+ * read first because it survives a rename, where the name a session carries is
233
+ * derived from what it turned out to be doing and rotates while a build runs.
234
+ * `CLAUDE_CODE_HOST_SESSION_ID` is deliberately never read: it holds a
235
+ * `local_`-prefixed value from the harness namespace that matches no row, and
236
+ * it is the variable a reader searching the environment for a session id finds
237
+ * first.
238
+ *
239
+ * The socket path is the last rung because its basename is the caller's pid by
240
+ * a client convention rather than a published interface, so a client that
241
+ * moves it drops this rung while leaving the two above it standing.
242
+ */
243
+ export function callerIdentity(env: Env = process.env): SelfIdentity {
244
+ const stated = env.CLAUDE_CODE_SESSION_ID?.trim()
245
+ const socket = env.CLAUDE_CODE_MESSAGING_SOCKET
246
+
247
+ return {
248
+ sessionId: stated !== undefined && stated.length > 0 ? stated : null,
249
+ pid:
250
+ pidOf(env.CLAUDE_PID) ??
251
+ pidOf(
252
+ socket === undefined
253
+ ? undefined
254
+ : basename(socket).replace(/\.sock$/, ''),
255
+ ),
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Names which row of a roster belongs to the caller.
261
+ *
262
+ * The read that already returns every field marks none of them as the caller,
263
+ * so this performs the join rather than adding a source. An identity the
264
+ * environment does not carry and an identity no row matches are separated,
265
+ * because the first is a client that states nothing and the second is a
266
+ * session the roster cannot see. A session driving from Remote Control is the
267
+ * measured instance of the second: it is addressable on the message channel
268
+ * and holds no local process record for the roster to report.
269
+ */
270
+ export function selfOf(
271
+ sessions: readonly ResolvedSession[],
272
+ identity: SelfIdentity,
273
+ ): SelfReport {
274
+ if (identity.sessionId === null && identity.pid === null) {
275
+ return { kind: 'unresolved', reason: 'no-identity', identity }
276
+ }
277
+
278
+ const byId =
279
+ identity.sessionId === null
280
+ ? undefined
281
+ : sessions.find((session) => session.sessionId === identity.sessionId)
282
+ const byPid =
283
+ identity.pid === null
284
+ ? undefined
285
+ : sessions.find((session) => session.pid === identity.pid)
286
+ const match = byId ?? byPid
287
+
288
+ return match === undefined
289
+ ? { kind: 'unresolved', reason: 'no-row', identity }
290
+ : { kind: 'self', session: match }
291
+ }