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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +15 -12
- package/README_CN.md +15 -12
- package/bootstrap-lite.md +3 -4
- package/bootstrap.md +3 -4
- package/gemini-extension.json +1 -1
- package/install.ps1 +3 -1
- package/install.sh +3 -1
- package/package.json +1 -1
- package/scripts/cli-codex-config.mjs +1 -26
- package/scripts/cli-codex.mjs +0 -10
- package/scripts/cli-doctor-codex.mjs +1 -7
- package/scripts/notify.mjs +4 -0
- package/scripts/project-session-cleanup.mjs +20 -54
- package/scripts/project-storage.mjs +2 -8
- package/scripts/runtime-scope.mjs +227 -50
- package/scripts/session-capsule.mjs +33 -12
- package/scripts/session-token.mjs +33 -1
- package/scripts/state-document.mjs +13 -58
- package/scripts/turn-state.mjs +2 -3
- package/scripts/workflow-core.mjs +1 -4
- package/scripts/workflow-plan-files.mjs +1 -1
|
@@ -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 {
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
370
|
-
|
|
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
|
-
|
|
373
|
-
|
|
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
|
-
|
|
376
|
-
|
|
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.
|
|
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
|
|
386
|
-
|
|
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
|
|
393
|
-
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
|
|
405
|
-
|
|
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
|
-
|
|
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
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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 (
|
|
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
|
-
|
|
428
|
-
|
|
590
|
+
if (envToken) {
|
|
591
|
+
return {
|
|
592
|
+
session: buildScopedSessionToken('host', envToken),
|
|
593
|
+
sessionMode: 'host-session',
|
|
594
|
+
}
|
|
595
|
+
}
|
|
429
596
|
|
|
430
|
-
|
|
431
|
-
|
|
597
|
+
if (envAliasToken) {
|
|
598
|
+
return {
|
|
599
|
+
session: buildScopedSessionToken('alias', envAliasToken),
|
|
600
|
+
sessionMode: 'alias-session',
|
|
601
|
+
}
|
|
602
|
+
}
|
|
432
603
|
|
|
433
|
-
|
|
434
|
-
if (
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
}
|
|
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
|
|
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
|
-
|
|
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.
|
|
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).
|
|
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
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
|
201
|
-
if (!existsSync(
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
15
|
+
return {
|
|
16
|
+
body: normalizeText(readFileSync(filePath, 'utf-8')),
|
|
17
|
+
}
|
|
61
18
|
}
|
|
62
19
|
|
|
63
|
-
export function composeStateDocument({
|
|
20
|
+
export function composeStateDocument({ body = '' } = {}) {
|
|
64
21
|
const normalizedBody = normalizeText(body).replace(/^\n+/, '')
|
|
65
|
-
return
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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, {
|
|
29
|
+
export function writeStateDocument(filePath, { body = '' } = {}) {
|
|
75
30
|
mkdirSync(dirname(filePath), { recursive: true })
|
|
76
|
-
writeFileSync(filePath, composeStateDocument({
|
|
31
|
+
writeFileSync(filePath, composeStateDocument({ body }), 'utf-8')
|
|
77
32
|
}
|
package/scripts/turn-state.mjs
CHANGED
|
@@ -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:
|
|
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:
|
|
290
|
+
path: getRuntimeScope(cwd, input).statePath,
|
|
292
291
|
payload,
|
|
293
292
|
}))
|
|
294
293
|
return
|