helloagents 3.0.37 → 3.0.39

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.
@@ -4,7 +4,15 @@ import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync,
4
4
  import { dirname, join, normalize, resolve } from 'node:path'
5
5
  import { homedir } from 'node:os'
6
6
 
7
- import { resolveProjectSessionToken, resolveSessionToken } from './session-token.mjs'
7
+ import {
8
+ PROJECT_CONVERSATION_PAYLOAD_KEYS,
9
+ PROJECT_SESSION_PAYLOAD_KEYS,
10
+ PROJECT_THREAD_PAYLOAD_KEYS,
11
+ resolveProjectSessionAliasToken,
12
+ resolveProjectSessionToken,
13
+ resolveSessionToken,
14
+ sanitizeSessionToken,
15
+ } from './session-token.mjs'
8
16
  import { USER_RUNTIME_MAX_AGE_MS } from './runtime-ttl.mjs'
9
17
  import { cleanupUserRuntimeRoot, getUserRuntimeRoot } from './runtime-user-cleanup.mjs'
10
18
  import { FULL_CARRIER_PROFILE_MARKER } from './cli-utils.mjs'
@@ -14,6 +22,7 @@ export const PROJECT_SESSIONS_DIR_NAME = 'sessions'
14
22
  export const PROJECT_ARTIFACTS_DIR_NAME = 'artifacts'
15
23
  export const EVENTS_FILE_NAME = 'events.jsonl'
16
24
  export const ACTIVE_SESSION_FILE_NAME = 'active.json'
25
+ export const PROJECT_RUNTIME_FILE_NAME = 'runtime.json'
17
26
  export const DEFAULT_STATE_SESSION_TOKEN = 'default'
18
27
  export const USER_RUNTIME_DIR_NAME = 'runtime'
19
28
  export { cleanupUserRuntimeRoot, getUserRuntimeRoot, USER_RUNTIME_MAX_AGE_MS }
@@ -329,10 +338,50 @@ function resolvePayloadSessionToken(payload = {}) {
329
338
  })
330
339
  }
331
340
 
341
+ function readRawPayloadValue(payload = {}, key = '') {
342
+ if (!payload || typeof payload !== 'object') return ''
343
+ const value = payload[key]
344
+ if (typeof value === 'string') return value.trim()
345
+ if (typeof value === 'number') return String(value)
346
+ return ''
347
+ }
348
+
349
+ function readPayloadSessionIdentity(payload = {}) {
350
+ const groups = [
351
+ ['session', PROJECT_SESSION_PAYLOAD_KEYS],
352
+ ['conversation', PROJECT_CONVERSATION_PAYLOAD_KEYS],
353
+ ['thread', PROJECT_THREAD_PAYLOAD_KEYS],
354
+ ]
355
+
356
+ for (const [kind, keys] of groups) {
357
+ for (const key of keys) {
358
+ const value = sanitizeRuntimeSegment(sanitizeSessionToken(readRawPayloadValue(payload, key)), '')
359
+ if (value) return { kind, token: value }
360
+ }
361
+ }
362
+
363
+ return { kind: '', token: '' }
364
+ }
365
+
332
366
  function resolveEnvSessionToken(env = process.env) {
333
367
  return resolveProjectSessionToken({ payload: {}, env })
334
368
  }
335
369
 
370
+ function resolveEnvSessionAliasToken(env = process.env) {
371
+ return resolveProjectSessionAliasToken({ env })
372
+ }
373
+
374
+ function resolveProjectSessionHostHint({ env = process.env, ppid = process.ppid } = {}) {
375
+ const envToken = sanitizeRuntimeSegment(resolveEnvSessionToken(env), '')
376
+ if (envToken) return `host:${envToken}`
377
+
378
+ const envAliasToken = sanitizeRuntimeSegment(resolveEnvSessionAliasToken(env), '')
379
+ if (envAliasToken) return `alias:${envAliasToken}`
380
+
381
+ const parentToken = sanitizeRuntimeSegment(String(ppid || '').trim(), '')
382
+ return parentToken ? `ppid:${parentToken}` : ''
383
+ }
384
+
336
385
  function resolveTransientSessionToken({ payload = {}, env = process.env, ppid = process.ppid } = {}) {
337
386
  return resolveSessionToken({
338
387
  payload,
@@ -342,11 +391,40 @@ function resolveTransientSessionToken({ payload = {}, env = process.env, ppid =
342
391
  })
343
392
  }
344
393
 
394
+ function buildScopedSessionToken(kind = '', raw = '') {
395
+ const normalizedKind = sanitizeRuntimeSegment(kind, 'session')
396
+ const value = sanitizeRuntimeSegment(String(raw || '').trim(), '')
397
+ if (!value) return ''
398
+ return `${normalizedKind}-${value}`
399
+ }
400
+
401
+ function buildSessionAliasKeys({ payload = {}, env = process.env } = {}) {
402
+ const keys = []
403
+ const payloadIdentity = readPayloadSessionIdentity(payload)
404
+ if (payloadIdentity.token) {
405
+ keys.push(`${payloadIdentity.kind}:${payloadIdentity.token}`)
406
+ }
407
+
408
+ const envSession = sanitizeRuntimeSegment(resolveEnvSessionToken(env), '')
409
+ if (envSession && !payloadIdentity.token) keys.push(`host:${envSession}`)
410
+
411
+ const payloadAlias = sanitizeRuntimeSegment(sanitizeSessionToken(payload?._helloagentsSessionAlias), '')
412
+ if (payloadAlias) keys.push(`alias:${payloadAlias}`)
413
+
414
+ const envAlias = sanitizeRuntimeSegment(resolveEnvSessionAliasToken(env), '')
415
+ if (envAlias && envSession && envAlias === envSession) {
416
+ return [...new Set(keys.filter(Boolean))]
417
+ }
418
+ if (envAlias) keys.push(`alias:${envAlias}`)
419
+
420
+ return [...new Set(keys.filter(Boolean))]
421
+ }
422
+
345
423
  function getActiveSessionPath(activationDir) {
346
424
  return join(activationDir, PROJECT_SESSIONS_DIR_NAME, ACTIVE_SESSION_FILE_NAME)
347
425
  }
348
426
 
349
- function resolveActiveSessionToken({ activationDir, projectRoot, workspace, now = Date.now() } = {}) {
427
+ function readActiveProjectSession({ activationDir, projectRoot, workspace, now = Date.now() } = {}) {
350
428
  const active = readJsonFile(getActiveSessionPath(activationDir), null)
351
429
  if (!active || typeof active !== 'object') return ''
352
430
  if (active.cwd && !samePath(active.cwd, projectRoot)) return ''
@@ -357,42 +435,77 @@ function resolveActiveSessionToken({ activationDir, projectRoot, workspace, now
357
435
  const updatedAt = Date.parse(active.updatedAt || '')
358
436
  if (!Number.isFinite(updatedAt) || now - updatedAt > USER_RUNTIME_MAX_AGE_MS) return ''
359
437
 
360
- return sanitizeRuntimeSegment(active.session, '')
438
+ return active
361
439
  }
362
440
 
363
441
  function resolveActiveAliasSession({ activationDir, projectRoot, workspace, alias, now = Date.now() } = {}) {
364
442
  if (!alias) return ''
365
- const active = readJsonFile(getActiveSessionPath(activationDir), null)
443
+ const active = readActiveProjectSession({
444
+ activationDir,
445
+ projectRoot,
446
+ workspace,
447
+ now,
448
+ })
366
449
  if (!active || typeof active !== 'object') return ''
367
- if (active.cwd && !samePath(active.cwd, projectRoot)) return ''
368
450
 
369
- const activeWorkspace = sanitizeRuntimeSegment(active.workspace || active.branch || '', '')
370
- if (activeWorkspace && activeWorkspace !== workspace) return ''
451
+ const aliases = active.aliases && typeof active.aliases === 'object' ? active.aliases : {}
452
+ const mapped = sanitizeRuntimeSegment(aliases[alias], '')
453
+ if (mapped) return mapped
454
+ if (
455
+ Object.prototype.hasOwnProperty.call(aliases, alias)
456
+ && resolveActiveSessionToken(active, join(activationDir, PROJECT_SESSIONS_DIR_NAME, workspace)) === DEFAULT_STATE_SESSION_TOKEN
457
+ ) {
458
+ return DEFAULT_STATE_SESSION_TOKEN
459
+ }
460
+ return ''
461
+ }
371
462
 
372
- const updatedAt = Date.parse(active.updatedAt || '')
373
- if (!Number.isFinite(updatedAt) || now - updatedAt > USER_RUNTIME_MAX_AGE_MS) return ''
463
+ function choosePreferredProjectSession(activeSession = '', candidates = []) {
464
+ for (const candidate of candidates) {
465
+ if (!candidate) continue
466
+ if (activeSession && candidate === activeSession) return candidate
467
+ }
468
+ return candidates.find(Boolean) || ''
469
+ }
374
470
 
375
- const aliases = active.aliases && typeof active.aliases === 'object' ? active.aliases : {}
376
- return sanitizeRuntimeSegment(aliases[alias], '')
471
+ function resolveActiveSessionToken(active = {}, workspaceDir = '') {
472
+ const session = sanitizeRuntimeSegment(active?.session || '', '')
473
+ if (session) return session
474
+
475
+ const defaultStatePath = workspaceDir
476
+ ? join(workspaceDir, DEFAULT_STATE_SESSION_TOKEN, 'STATE.md')
477
+ : ''
478
+ return defaultStatePath && existsSync(defaultStatePath)
479
+ ? DEFAULT_STATE_SESSION_TOKEN
480
+ : ''
377
481
  }
378
482
 
379
- export function writeActiveProjectSession(scope, { host = '', source = '', env = process.env } = {}) {
380
- if (!scope?.active || !scope.activationDir || !scope.session) return ''
483
+ export function writeActiveProjectSession(scope, { host = '', source = '', payload = {}, env = process.env, ppid = process.ppid } = {}) {
484
+ if (!scope?.active || !scope.activationDir || !scope.workspace) return ''
381
485
 
382
486
  const activePath = getActiveSessionPath(scope.activationDir)
383
487
  const current = readJsonFile(activePath, null) || {}
384
- const aliases = current.aliases && typeof current.aliases === 'object' ? current.aliases : {}
385
- const envToken = sanitizeRuntimeSegment(resolveEnvSessionToken(env), '')
386
- if (envToken && envToken !== scope.session) aliases[envToken] = scope.session
387
-
488
+ const aliases = current.aliases && typeof current.aliases === 'object' ? { ...current.aliases } : {}
489
+ const session = scope.session || DEFAULT_STATE_SESSION_TOKEN
490
+ const sessionMode = scope.session
491
+ ? scope.sessionMode
492
+ : 'default'
493
+ const hostHint = resolveProjectSessionHostHint({ env, ppid }) || current.hostHint || ''
494
+ const aliasKeys = buildSessionAliasKeys({ payload, env })
495
+ for (const aliasKey of aliasKeys) {
496
+ aliases[aliasKey] = session
497
+ const [, aliasValue = ''] = String(aliasKey).split(':')
498
+ if (aliasValue) aliases[aliasValue] = session
499
+ }
388
500
  writeJsonFileAtomic(activePath, {
389
501
  version: 1,
390
502
  cwd: scope.cwd,
391
503
  workspace: scope.workspace || scope.branch,
392
- session: scope.session,
393
- sessionMode: scope.sessionMode,
504
+ session,
505
+ sessionMode,
394
506
  host,
395
507
  source,
508
+ ...(hostHint ? { hostHint } : {}),
396
509
  aliases,
397
510
  ...(current.cleanupCheckedAt ? { cleanupCheckedAt: current.cleanupCheckedAt } : {}),
398
511
  updatedAt: new Date().toISOString(),
@@ -400,60 +513,121 @@ export function writeActiveProjectSession(scope, { host = '', source = '', env =
400
513
  return activePath
401
514
  }
402
515
 
403
- function chooseProjectSession({ payload, env, activationDir, projectRoot, workspace }) {
404
- const payloadToken = sanitizeRuntimeSegment(resolvePayloadSessionToken(payload), '')
405
- if (payloadToken) return { session: payloadToken, sessionMode: 'host-session' }
406
-
407
- const payloadAlias = sanitizeRuntimeSegment(payload?._helloagentsSessionAlias, '')
408
- const payloadAliasToken = resolveActiveAliasSession({
516
+ function chooseProjectSession({ payload, env, ppid, activationDir, projectRoot, workspace }) {
517
+ const workspaceDir = join(activationDir, PROJECT_SESSIONS_DIR_NAME, workspace)
518
+ const active = readActiveProjectSession({
409
519
  activationDir,
410
520
  projectRoot,
411
521
  workspace,
412
- alias: payloadAlias,
413
522
  })
414
- if (payloadAliasToken) return { session: payloadAliasToken, sessionMode: 'active-session' }
523
+ const activeSession = resolveActiveSessionToken(active, workspaceDir)
524
+ const payloadIdentity = readPayloadSessionIdentity(payload)
525
+ const payloadToken = payloadIdentity.token
526
+ const payloadAlias = sanitizeRuntimeSegment(sanitizeSessionToken(payload?._helloagentsSessionAlias), '')
527
+ const payloadAliases = [
528
+ payloadIdentity.token ? `${payloadIdentity.kind}:${payloadIdentity.token}` : '',
529
+ payloadAlias ? `alias:${payloadAlias}` : '',
530
+ ].filter(Boolean)
531
+ const payloadMappedSession = choosePreferredProjectSession(
532
+ activeSession,
533
+ payloadAliases.map((alias) => resolveActiveAliasSession({
534
+ activationDir,
535
+ projectRoot,
536
+ workspace,
537
+ alias,
538
+ })),
539
+ )
540
+ if (payloadMappedSession) {
541
+ return { session: payloadMappedSession, sessionMode: 'active-session' }
542
+ }
415
543
 
416
544
  const envToken = sanitizeRuntimeSegment(resolveEnvSessionToken(env), '')
417
- const aliasToken = resolveActiveAliasSession({
418
- activationDir,
419
- projectRoot,
420
- workspace,
421
- alias: envToken,
422
- })
423
- if (aliasToken) return { session: aliasToken, sessionMode: 'active-session' }
545
+ const envAliasToken = sanitizeRuntimeSegment(resolveEnvSessionAliasToken(env), '')
546
+ const envMappedSession = choosePreferredProjectSession(
547
+ activeSession,
548
+ [
549
+ envToken ? resolveActiveAliasSession({
550
+ activationDir,
551
+ projectRoot,
552
+ workspace,
553
+ alias: `host:${envToken}`,
554
+ }) : '',
555
+ envAliasToken ? resolveActiveAliasSession({
556
+ activationDir,
557
+ projectRoot,
558
+ workspace,
559
+ alias: `alias:${envAliasToken}`,
560
+ }) : '',
561
+ ],
562
+ )
563
+ if (envMappedSession) return { session: envMappedSession, sessionMode: 'active-session' }
564
+
565
+ if (
566
+ activeSession === DEFAULT_STATE_SESSION_TOKEN
567
+ && active?.hostHint
568
+ && active.hostHint === resolveProjectSessionHostHint({ env, ppid })
569
+ && (payloadToken || payloadAlias || envToken || envAliasToken)
570
+ ) {
571
+ return {
572
+ session: activeSession,
573
+ sessionMode: 'active-session',
574
+ }
575
+ }
424
576
 
425
- if (envToken) return { session: envToken, sessionMode: 'host-session' }
577
+ if (payloadToken) {
578
+ return {
579
+ session: buildScopedSessionToken('host', payloadToken),
580
+ sessionMode: 'host-session',
581
+ }
582
+ }
583
+ if (payloadAlias) {
584
+ return {
585
+ session: buildScopedSessionToken('alias', payloadAlias),
586
+ sessionMode: 'alias-session',
587
+ }
588
+ }
426
589
 
427
- const activeToken = resolveActiveSessionToken({ activationDir, projectRoot, workspace })
428
- if (activeToken) return { session: activeToken, sessionMode: 'active-session' }
590
+ if (envToken) {
591
+ return {
592
+ session: buildScopedSessionToken('host', envToken),
593
+ sessionMode: 'host-session',
594
+ }
595
+ }
429
596
 
430
- return { session: DEFAULT_STATE_SESSION_TOKEN, sessionMode: 'default' }
431
- }
597
+ if (envAliasToken) {
598
+ return {
599
+ session: buildScopedSessionToken('alias', envAliasToken),
600
+ sessionMode: 'alias-session',
601
+ }
602
+ }
432
603
 
433
- function removeLegacyProjectArtifacts(activationDir) {
434
- if (!activationDir) return
435
- const artifactsDir = join(activationDir, PROJECT_ARTIFACTS_DIR_NAME)
436
- if (!existsSync(artifactsDir)) return
437
- try {
438
- rmSync(artifactsDir, { recursive: true, force: true })
439
- } catch {}
604
+ const source = String(payload?.source || '').trim().toLowerCase()
605
+ if ((source === 'resume' || source === 'compact') && activeSession) {
606
+ return {
607
+ session: activeSession,
608
+ sessionMode: 'active-session',
609
+ }
610
+ }
611
+
612
+ return { session: '', sessionMode: 'unidentified' }
440
613
  }
441
614
 
442
615
  export function getProjectSessionScope(cwd, options = {}) {
443
616
  const normalizedCwd = normalizePath(cwd || process.cwd())
444
617
  const projectRoot = getProjectRoot(normalizedCwd)
445
- const { payload = {}, env = process.env } = normalizeRuntimeOptions(options)
618
+ const { payload = {}, env = process.env, ppid = process.ppid } = normalizeRuntimeOptions(options)
446
619
  const activationDir = getProjectActivationDir(projectRoot)
447
- removeLegacyProjectArtifacts(activationDir)
448
620
  const workspace = resolveWorkspaceName(projectRoot)
449
621
  const { session, sessionMode } = chooseProjectSession({
450
622
  payload,
451
623
  env,
624
+ ppid,
452
625
  activationDir,
453
626
  projectRoot,
454
627
  workspace,
455
628
  })
456
- const sessionDir = join(activationDir, PROJECT_SESSIONS_DIR_NAME, workspace, session)
629
+ const workspaceDir = join(activationDir, PROJECT_SESSIONS_DIR_NAME, workspace)
630
+ const sessionDir = session ? join(workspaceDir, session) : join(workspaceDir, DEFAULT_STATE_SESSION_TOKEN)
457
631
 
458
632
  return {
459
633
  cwd: projectRoot,
@@ -464,10 +638,12 @@ export function getProjectSessionScope(cwd, options = {}) {
464
638
  sessionMode,
465
639
  activationDir,
466
640
  sessionDir,
641
+ workspaceDir,
467
642
  statePath: join(sessionDir, 'STATE.md'),
468
643
  eventsPath: join(sessionDir, EVENTS_FILE_NAME),
469
644
  artifactsDir: join(sessionDir, PROJECT_ARTIFACTS_DIR_NAME),
470
- key: `${projectRoot}::${workspace}::${session}`,
645
+ runtimePath: join(sessionDir, PROJECT_RUNTIME_FILE_NAME),
646
+ key: `${projectRoot}::${workspace}::${session || DEFAULT_STATE_SESSION_TOKEN}`,
471
647
  }
472
648
  }
473
649
 
@@ -497,6 +673,7 @@ function buildTransientRuntimeDir(cwd, options = {}) {
497
673
  statePath: join(getUserRuntimeRoot(), hash, 'STATE.md'),
498
674
  eventsPath: join(getUserRuntimeRoot(), hash, EVENTS_FILE_NAME),
499
675
  artifactsDir: join(getUserRuntimeRoot(), hash, PROJECT_ARTIFACTS_DIR_NAME),
676
+ runtimePath: join(getUserRuntimeRoot(), hash, PROJECT_RUNTIME_FILE_NAME),
500
677
  key: `${normalizedCwd}::transient::${token}`,
501
678
  }
502
679
  }
@@ -31,6 +31,18 @@ function buildEmptyCapsule(scope) {
31
31
  }
32
32
  }
33
33
 
34
+ function readRuntimeDocument(filePath) {
35
+ const payload = readJsonFile(filePath, null)
36
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
37
+ return null
38
+ }
39
+ return payload
40
+ }
41
+
42
+ function writeRuntimeDocument(filePath, payload) {
43
+ writeJsonFileAtomic(filePath, payload)
44
+ }
45
+
34
46
  function normalizeOptions(options = {}) {
35
47
  if (!options || typeof options !== 'object') return {}
36
48
  if (options.payload && typeof options.payload === 'object') return options
@@ -41,7 +53,7 @@ function normalizeOptions(options = {}) {
41
53
  }
42
54
 
43
55
  function getEventSessionAlias(eventPayload = {}) {
44
- return eventPayload.sessionId || eventPayload.session_id || eventPayload['session-id'] || ''
56
+ return eventPayload.sessionAlias || eventPayload.session_alias || eventPayload['session-alias'] || eventPayload._helloagentsSessionAlias || ''
45
57
  }
46
58
 
47
59
  function getScope(cwd, options = {}) {
@@ -84,7 +96,7 @@ function shouldMaterializeSessionState(options = {}) {
84
96
  }
85
97
 
86
98
  export function getSessionCapsulePath(cwd = process.cwd(), options = {}) {
87
- return getScope(cwd, options).statePath
99
+ return getScope(cwd, options).runtimePath
88
100
  }
89
101
 
90
102
  export function getSessionEventsPath(cwd = process.cwd(), options = {}) {
@@ -102,15 +114,14 @@ export function getSessionArtifactPath(cwd, fileName, options = {}) {
102
114
  export function getSessionArtifactRelativePath(cwd, fileName, options = {}) {
103
115
  const scope = getScope(cwd, options)
104
116
  if (scope.scope === 'project-session') {
105
- return `.helloagents/sessions/${scope.workspace || scope.branch}/${scope.session}/artifacts/${fileName}`
117
+ return `.helloagents/sessions/${scope.workspace || scope.branch}/${scope.session || 'default'}/artifacts/${fileName}`
106
118
  }
107
119
  return `~/.helloagents/runtime/${basename(scope.sessionDir)}/artifacts/${fileName}`
108
120
  }
109
121
 
110
122
  export function readSessionCapsule(cwd = process.cwd(), options = {}) {
111
123
  const scope = getScope(cwd, options)
112
- const { metadata } = readStateDocument(scope.statePath)
113
- const capsule = metadata && typeof metadata === 'object' ? metadata : null
124
+ const capsule = readRuntimeDocument(scope.runtimePath)
114
125
  if (!capsule || Array.isArray(capsule)) return buildEmptyCapsule(scope)
115
126
  return {
116
127
  ...buildEmptyCapsule(scope),
@@ -165,12 +176,16 @@ export function writeSessionCapsule(cwd, capsule, options = {}) {
165
176
  sessionMode: scope.sessionMode,
166
177
  updatedAt: new Date().toISOString(),
167
178
  }
168
- writeStateDocument(scope.statePath, {
169
- metadata: nextCapsule,
170
- body: currentDocument.body,
171
- })
179
+ writeRuntimeDocument(scope.runtimePath, nextCapsule)
180
+ if (hasBody) {
181
+ writeStateDocument(scope.statePath, {
182
+ body: currentDocument.body,
183
+ })
184
+ }
172
185
  writeActiveProjectSession(scope, {
186
+ payload: normalizedOptions.payload,
173
187
  env: normalizedOptions.env,
188
+ ppid: normalizedOptions.ppid,
174
189
  })
175
190
  return nextCapsule
176
191
  }
@@ -197,8 +212,8 @@ export function writeCapsuleSection(cwd, section, value, options = {}) {
197
212
  }
198
213
 
199
214
  export function clearCapsuleSection(cwd, section, options = {}) {
200
- const statePath = getSessionCapsulePath(cwd, options)
201
- if (!existsSync(statePath)) return false
215
+ const runtimePath = getSessionCapsulePath(cwd, options)
216
+ if (!existsSync(runtimePath)) return false
202
217
 
203
218
  const capsule = readSessionCapsule(cwd, options)
204
219
  if (!Object.prototype.hasOwnProperty.call(capsule, section)) return false
@@ -229,7 +244,9 @@ export function appendSessionEvent(cwd, eventPayload, options = {}) {
229
244
  writeActiveProjectSession(scope, {
230
245
  host: eventPayload.host || '',
231
246
  source: eventPayload.source || eventName,
247
+ payload: scopedOptions.payload,
232
248
  env: scopedOptions.env,
249
+ ppid: scopedOptions.ppid,
233
250
  })
234
251
  if (!shouldRecordSessionEvents(scopedOptions)) return ''
235
252
 
@@ -289,7 +306,11 @@ export function clearSessionArtifact(cwd, fileName, options = {}) {
289
306
  }
290
307
 
291
308
  export function removeSessionCapsule(cwd, options = {}) {
292
- removeRuntimeFile(getSessionCapsulePath(cwd, options))
309
+ const scope = getScope(cwd, options)
310
+ removeRuntimeFile(scope.runtimePath)
311
+ if (scope.scope !== 'project-session') {
312
+ removeRuntimeFile(scope.statePath)
313
+ }
293
314
  }
294
315
 
295
316
  function shouldRecordSessionEvents(options = {}) {
@@ -17,19 +17,31 @@ const PAYLOAD_SESSION_KEYS = [
17
17
  'tab',
18
18
  ]
19
19
 
20
- const PROJECT_PAYLOAD_SESSION_KEYS = [
20
+ const PROJECT_SESSION_PAYLOAD_KEYS = [
21
21
  'sessionId',
22
22
  'session_id',
23
23
  'session',
24
+ ]
25
+
26
+ const PROJECT_CONVERSATION_PAYLOAD_KEYS = [
24
27
  'conversationId',
25
28
  'conversation_id',
26
29
  'conversation',
30
+ ]
31
+
32
+ const PROJECT_THREAD_PAYLOAD_KEYS = [
27
33
  'threadId',
28
34
  'thread_id',
29
35
  'thread-id',
30
36
  'thread',
31
37
  ]
32
38
 
39
+ const PROJECT_PAYLOAD_SESSION_KEYS = [
40
+ ...PROJECT_SESSION_PAYLOAD_KEYS,
41
+ ...PROJECT_CONVERSATION_PAYLOAD_KEYS,
42
+ ...PROJECT_THREAD_PAYLOAD_KEYS,
43
+ ]
44
+
33
45
  const ENV_SESSION_KEYS = [
34
46
  'HELLOAGENTS_NOTIFY_SESSION_ID',
35
47
  'WT_SESSION',
@@ -45,6 +57,16 @@ const PROJECT_ENV_SESSION_KEYS = [
45
57
  'HELLOAGENTS_NOTIFY_SESSION_ID',
46
58
  ]
47
59
 
60
+ const PROJECT_ALIAS_ENV_SESSION_KEYS = [
61
+ 'WT_SESSION',
62
+ 'TERM_SESSION_ID',
63
+ 'KITTY_WINDOW_ID',
64
+ 'ALACRITTY_WINDOW_ID',
65
+ 'WINDOWID',
66
+ 'WEZTERM_PANE',
67
+ 'TAB_ID',
68
+ ]
69
+
48
70
  function readStringCandidate(input, key) {
49
71
  if (!input || typeof input !== 'object') return ''
50
72
  const value = input[key]
@@ -99,9 +121,19 @@ export function resolveProjectSessionToken({
99
121
  return resolveTokenFromKeys(env, PROJECT_ENV_SESSION_KEYS)
100
122
  }
101
123
 
124
+ export function resolveProjectSessionAliasToken({
125
+ env = process.env,
126
+ } = {}) {
127
+ return resolveTokenFromKeys(env, PROJECT_ALIAS_ENV_SESSION_KEYS)
128
+ }
129
+
102
130
  export {
103
131
  ENV_SESSION_KEYS,
104
132
  PAYLOAD_SESSION_KEYS,
133
+ PROJECT_ALIAS_ENV_SESSION_KEYS,
134
+ PROJECT_CONVERSATION_PAYLOAD_KEYS,
105
135
  PROJECT_ENV_SESSION_KEYS,
106
136
  PROJECT_PAYLOAD_SESSION_KEYS,
137
+ PROJECT_SESSION_PAYLOAD_KEYS,
138
+ PROJECT_THREAD_PAYLOAD_KEYS,
107
139
  }
@@ -1,77 +1,32 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
2
2
  import { dirname } from 'node:path'
3
3
 
4
- const STATE_META_BEGIN = '<!-- HELLOAGENTS_STATE_META'
5
- const STATE_META_END = 'HELLOAGENTS_STATE_META -->'
4
+ export const AUTO_CREATED_STATE_MARKER = '由运行时自动创建;后续按实际任务重写'
6
5
 
7
6
  function normalizeText(content = '') {
8
7
  return String(content || '').replace(/^\uFEFF/, '')
9
8
  }
10
9
 
11
- function splitLines(content = '') {
12
- return normalizeText(content).replace(/\r\n/g, '\n').split('\n')
13
- }
14
-
15
- export function parseStateDocument(content = '') {
16
- const lines = splitLines(content)
17
- if (lines[0]?.trim() !== STATE_META_BEGIN) {
18
- return {
19
- hasMetadata: false,
20
- metadata: null,
21
- body: normalizeText(content),
22
- }
23
- }
24
-
25
- const endIndex = lines.findIndex((line, index) => index > 0 && line.trim() === STATE_META_END)
26
- if (endIndex < 0) {
27
- return {
28
- hasMetadata: false,
29
- metadata: null,
30
- body: normalizeText(content),
31
- }
32
- }
33
-
34
- const metadataText = lines.slice(1, endIndex).join('\n').trim()
35
- const body = lines.slice(endIndex + 1).join('\n').replace(/^\n+/, '')
36
- try {
37
- return {
38
- hasMetadata: true,
39
- metadata: JSON.parse(metadataText),
40
- body,
41
- }
42
- } catch {
43
- return {
44
- hasMetadata: false,
45
- metadata: null,
46
- body,
47
- }
48
- }
49
- }
50
-
51
10
  export function readStateDocument(filePath) {
52
11
  if (!filePath || !existsSync(filePath)) {
53
- return {
54
- hasMetadata: false,
55
- metadata: null,
56
- body: '',
57
- }
12
+ return { body: '' }
58
13
  }
59
14
 
60
- return parseStateDocument(readFileSync(filePath, 'utf-8'))
15
+ return {
16
+ body: normalizeText(readFileSync(filePath, 'utf-8')),
17
+ }
61
18
  }
62
19
 
63
- export function composeStateDocument({ metadata = {}, body = '' } = {}) {
20
+ export function composeStateDocument({ body = '' } = {}) {
64
21
  const normalizedBody = normalizeText(body).replace(/^\n+/, '')
65
- return [
66
- STATE_META_BEGIN,
67
- JSON.stringify(metadata, null, 2),
68
- STATE_META_END,
69
- '',
70
- normalizedBody,
71
- ].join('\n').replace(/\n+$/, '\n')
22
+ return normalizedBody ? `${normalizedBody.replace(/\n+$/, '')}\n` : ''
23
+ }
24
+
25
+ export function looksLikeAutoCreatedState(body = '') {
26
+ return normalizeText(body).includes(AUTO_CREATED_STATE_MARKER)
72
27
  }
73
28
 
74
- export function writeStateDocument(filePath, { metadata = {}, body = '' } = {}) {
29
+ export function writeStateDocument(filePath, { body = '' } = {}) {
75
30
  mkdirSync(dirname(filePath), { recursive: true })
76
- writeFileSync(filePath, composeStateDocument({ metadata, body }), 'utf-8')
31
+ writeFileSync(filePath, composeStateDocument({ body }), 'utf-8')
77
32
  }
@@ -5,7 +5,6 @@ import { fileURLToPath } from 'node:url'
5
5
  import {
6
6
  appendSessionEvent,
7
7
  clearCapsuleSection,
8
- getSessionCapsulePath,
9
8
  getRuntimeScope,
10
9
  readCapsuleSection,
11
10
  writeCapsuleSection,
@@ -108,7 +107,7 @@ export function readTurnState(cwd = process.cwd(), { now = Date.now(), ...option
108
107
  return {
109
108
  cwd: normalizePath(entry.cwd),
110
109
  key: entry.key || '',
111
- path: getSessionCapsulePath(cwd, options),
110
+ path: getRuntimeScope(cwd, options).statePath,
112
111
  updatedAt: entry.updatedAt,
113
112
  ...normalized,
114
113
  }
@@ -288,7 +287,7 @@ function main() {
288
287
  const payload = writeTurnState(cwd, input)
289
288
  process.stdout.write(JSON.stringify({
290
289
  suppressOutput: true,
291
- path: getSessionCapsulePath(cwd, input),
290
+ path: getRuntimeScope(cwd, input).statePath,
292
291
  payload,
293
292
  }))
294
293
  return