helloagents 3.0.38 → 3.1.1
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 +17 -13
- package/README_CN.md +17 -13
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/scripts/cli-branch.mjs +2 -5
- package/scripts/cli-codex-config.mjs +187 -26
- package/scripts/cli-codex.mjs +10 -14
- package/scripts/cli-doctor-codex.mjs +18 -15
- package/scripts/cli-doctor.mjs +48 -15
- package/scripts/cli-host-detect.mjs +38 -5
- package/scripts/cli-lifecycle-hosts.mjs +48 -20
- package/scripts/cli-messages.mjs +9 -8
- package/scripts/cli-process.mjs +16 -0
- package/scripts/cli-runtime-root.mjs +55 -12
- package/scripts/cli-toml.mjs +4 -0
- package/scripts/project-session-cleanup.mjs +0 -32
- package/scripts/runtime-scope.mjs +185 -100
- package/scripts/session-capsule.mjs +6 -65
- package/scripts/session-token.mjs +16 -2
- package/scripts/state-document.mjs +7 -51
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { copyFileSync, existsSync, mkdtempSync, realpathSync, renameSync } from 'node:fs'
|
|
2
2
|
import { dirname, join, resolve } from 'node:path'
|
|
3
3
|
|
|
4
|
-
import { copyEntries, ensureDir, removeIfExists } from './cli-utils.mjs'
|
|
4
|
+
import { copyEntries, createLink, ensureDir, removeIfExists } from './cli-utils.mjs'
|
|
5
5
|
|
|
6
6
|
export const RUNTIME_ROOT_ENTRIES = [
|
|
7
7
|
'.claude-plugin',
|
|
@@ -28,6 +28,16 @@ export function getStableRuntimeRoot(home) {
|
|
|
28
28
|
return join(home, '.helloagents', 'helloagents')
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/** Return the Claude local marketplace projection root derived from the shared runtime copy. */
|
|
32
|
+
export function getClaudeMarketplaceRoot(home) {
|
|
33
|
+
return join(home, '.helloagents', 'host-projections', 'claude-marketplace')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Return the Gemini extension projection root derived from the shared runtime copy. */
|
|
37
|
+
export function getGeminiExtensionRoot(home) {
|
|
38
|
+
return join(home, '.helloagents', 'host-projections', 'gemini')
|
|
39
|
+
}
|
|
40
|
+
|
|
31
41
|
function normalizePath(path) {
|
|
32
42
|
const resolved = resolve(path)
|
|
33
43
|
try {
|
|
@@ -63,17 +73,9 @@ function retryTransientFs(operation) {
|
|
|
63
73
|
throw lastError
|
|
64
74
|
}
|
|
65
75
|
|
|
66
|
-
function materializeGeminiHooks
|
|
67
|
-
const source = join(root, 'hooks', 'hooks-gemini.json')
|
|
68
|
-
const target = join(root, 'hooks', 'hooks.json')
|
|
69
|
-
if (!existsSync(source)) return
|
|
70
|
-
copyFileSync(source, target)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** Sync package runtime files into the stable root without copying repo-only files. */
|
|
74
|
-
export function syncRuntimeRoot(sourceRoot, runtimeRoot) {
|
|
76
|
+
function syncRuntimeTree(sourceRoot, targetRoot, { materializeGeminiHooks = false } = {}) {
|
|
75
77
|
const source = resolve(sourceRoot)
|
|
76
|
-
const target = resolve(
|
|
78
|
+
const target = resolve(targetRoot)
|
|
77
79
|
if (samePath(source, target)) {
|
|
78
80
|
return { synced: false, root: target }
|
|
79
81
|
}
|
|
@@ -84,7 +86,13 @@ export function syncRuntimeRoot(sourceRoot, runtimeRoot) {
|
|
|
84
86
|
|
|
85
87
|
try {
|
|
86
88
|
copyEntries(source, staging, RUNTIME_ROOT_ENTRIES)
|
|
87
|
-
materializeGeminiHooks
|
|
89
|
+
if (materializeGeminiHooks) {
|
|
90
|
+
const sourceHooks = join(staging, 'hooks', 'hooks-gemini.json')
|
|
91
|
+
const targetHooks = join(staging, 'hooks', 'hooks.json')
|
|
92
|
+
if (existsSync(sourceHooks)) {
|
|
93
|
+
copyFileSync(sourceHooks, targetHooks)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
88
96
|
retryTransientFs(() => {
|
|
89
97
|
removeIfExists(target)
|
|
90
98
|
renameSync(staging, target)
|
|
@@ -96,7 +104,42 @@ export function syncRuntimeRoot(sourceRoot, runtimeRoot) {
|
|
|
96
104
|
}
|
|
97
105
|
}
|
|
98
106
|
|
|
107
|
+
/** Sync package runtime files into the stable root without copying repo-only files. */
|
|
108
|
+
export function syncRuntimeRoot(sourceRoot, runtimeRoot) {
|
|
109
|
+
return syncRuntimeTree(sourceRoot, runtimeRoot)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Sync a Claude local marketplace root that resolves to the stable runtime copy. */
|
|
113
|
+
export function syncClaudeMarketplaceRoot(sourceRoot, marketplaceRoot) {
|
|
114
|
+
const source = resolve(sourceRoot)
|
|
115
|
+
const target = resolve(marketplaceRoot)
|
|
116
|
+
if (samePath(source, target)) {
|
|
117
|
+
return { synced: false, root: target }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
removeIfExists(target)
|
|
121
|
+
if (createLink(source, target)) {
|
|
122
|
+
return { synced: true, root: target }
|
|
123
|
+
}
|
|
124
|
+
return syncRuntimeTree(source, target)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Sync a host-specific extension root derived from the stable runtime copy. */
|
|
128
|
+
export function syncGeminiExtensionRoot(sourceRoot, extensionRoot) {
|
|
129
|
+
return syncRuntimeTree(sourceRoot, extensionRoot, { materializeGeminiHooks: true })
|
|
130
|
+
}
|
|
131
|
+
|
|
99
132
|
/** Remove the stable runtime copy while leaving user settings under ~/.helloagents intact. */
|
|
100
133
|
export function removeRuntimeRoot(runtimeRoot) {
|
|
101
134
|
removeIfExists(runtimeRoot)
|
|
102
135
|
}
|
|
136
|
+
|
|
137
|
+
/** Remove the Claude marketplace projection root. */
|
|
138
|
+
export function removeClaudeMarketplaceRoot(home) {
|
|
139
|
+
removeIfExists(getClaudeMarketplaceRoot(home))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Remove the Gemini extension projection root. */
|
|
143
|
+
export function removeGeminiExtensionRoot(home) {
|
|
144
|
+
removeIfExists(getGeminiExtensionRoot(home))
|
|
145
|
+
}
|
package/scripts/cli-toml.mjs
CHANGED
|
@@ -117,6 +117,10 @@ export function removeTopLevelTomlBlock(text, key) {
|
|
|
117
117
|
return normalizeToml(`${normalized.slice(0, existing.start)}${normalized.slice(existing.end)}`);
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
export function hasTopLevelTomlBlock(text, key) {
|
|
121
|
+
return Boolean(findTopLevelTomlBlock(text, key));
|
|
122
|
+
}
|
|
123
|
+
|
|
120
124
|
export function prependTopLevelTomlBlocks(text, blocks) {
|
|
121
125
|
const normalizedBlocks = blocks
|
|
122
126
|
.map((block) => String(block || '').trim())
|
|
@@ -23,10 +23,6 @@ function removePath(filePath, result, bucket) {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
function isDebugLog(entryName = '') {
|
|
27
|
-
return /\.log$/i.test(entryName)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
26
|
function isDirectoryEmptyRecursive(dirPath) {
|
|
31
27
|
const entries = readdirSync(dirPath, { withFileTypes: true })
|
|
32
28
|
if (entries.length === 0) return true
|
|
@@ -92,32 +88,6 @@ function cleanupTransientSessionTemps(sessionsDir, result) {
|
|
|
92
88
|
}
|
|
93
89
|
}
|
|
94
90
|
|
|
95
|
-
function cleanupLegacyProjectArtifacts(activationDir, result) {
|
|
96
|
-
const artifactsDir = join(activationDir, 'artifacts')
|
|
97
|
-
if (!existsSync(artifactsDir)) return
|
|
98
|
-
|
|
99
|
-
let removableEntries = []
|
|
100
|
-
try {
|
|
101
|
-
removableEntries = readdirSync(artifactsDir, { withFileTypes: true })
|
|
102
|
-
} catch (error) {
|
|
103
|
-
result.errors.push(`${artifactsDir}: ${error.message}`)
|
|
104
|
-
return
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
for (const entry of removableEntries) {
|
|
108
|
-
if (!entry.isFile() || !isDebugLog(entry.name)) continue
|
|
109
|
-
removePath(join(artifactsDir, entry.name), result, 'removedLegacyArtifacts')
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
try {
|
|
113
|
-
if (isDirectoryEmptyRecursive(artifactsDir)) {
|
|
114
|
-
removePath(artifactsDir, result, 'removedLegacyArtifacts')
|
|
115
|
-
}
|
|
116
|
-
} catch (error) {
|
|
117
|
-
result.errors.push(`${artifactsDir}: ${error.message}`)
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
91
|
export function cleanupProjectSessions(cwd, { now = Date.now(), minIntervalMs = 0, maxAgeMs = PROJECT_SESSION_MAX_AGE_MS } = {}) {
|
|
122
92
|
const projectRoot = getProjectRoot(cwd)
|
|
123
93
|
const activationDir = getProjectActivationDir(projectRoot)
|
|
@@ -131,7 +101,6 @@ export function cleanupProjectSessions(cwd, { now = Date.now(), minIntervalMs =
|
|
|
131
101
|
removedNoStateDirs: [],
|
|
132
102
|
removedSeedDirs: [],
|
|
133
103
|
removedTempFiles: [],
|
|
134
|
-
removedLegacyArtifacts: [],
|
|
135
104
|
errors: [],
|
|
136
105
|
skipped: false,
|
|
137
106
|
}
|
|
@@ -150,7 +119,6 @@ export function cleanupProjectSessions(cwd, { now = Date.now(), minIntervalMs =
|
|
|
150
119
|
} catch (error) {
|
|
151
120
|
result.errors.push(`${sessionsDir}: ${error.message}`)
|
|
152
121
|
}
|
|
153
|
-
cleanupLegacyProjectArtifacts(activationDir, result)
|
|
154
122
|
|
|
155
123
|
for (const workspaceEntry of readdirSync(sessionsDir, { withFileTypes: true })) {
|
|
156
124
|
if (!workspaceEntry.isDirectory()) continue
|
|
@@ -5,14 +5,17 @@ import { dirname, join, normalize, resolve } from 'node:path'
|
|
|
5
5
|
import { homedir } from 'node:os'
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
|
+
PROJECT_CONVERSATION_PAYLOAD_KEYS,
|
|
9
|
+
PROJECT_SESSION_PAYLOAD_KEYS,
|
|
10
|
+
PROJECT_THREAD_PAYLOAD_KEYS,
|
|
8
11
|
resolveProjectSessionAliasToken,
|
|
9
12
|
resolveProjectSessionToken,
|
|
10
13
|
resolveSessionToken,
|
|
14
|
+
sanitizeSessionToken,
|
|
11
15
|
} from './session-token.mjs'
|
|
12
16
|
import { USER_RUNTIME_MAX_AGE_MS } from './runtime-ttl.mjs'
|
|
13
17
|
import { cleanupUserRuntimeRoot, getUserRuntimeRoot } from './runtime-user-cleanup.mjs'
|
|
14
18
|
import { FULL_CARRIER_PROFILE_MARKER } from './cli-utils.mjs'
|
|
15
|
-
import { readStateDocument, writeStateDocument } from './state-document.mjs'
|
|
16
19
|
|
|
17
20
|
export const PROJECT_DIR_NAME = '.helloagents'
|
|
18
21
|
export const PROJECT_SESSIONS_DIR_NAME = 'sessions'
|
|
@@ -21,7 +24,6 @@ export const EVENTS_FILE_NAME = 'events.jsonl'
|
|
|
21
24
|
export const ACTIVE_SESSION_FILE_NAME = 'active.json'
|
|
22
25
|
export const PROJECT_RUNTIME_FILE_NAME = 'runtime.json'
|
|
23
26
|
export const DEFAULT_STATE_SESSION_TOKEN = 'default'
|
|
24
|
-
export const LEGACY_SESSION_POINTERS_FILE_NAME = 'session-pointers.json'
|
|
25
27
|
export const USER_RUNTIME_DIR_NAME = 'runtime'
|
|
26
28
|
export { cleanupUserRuntimeRoot, getUserRuntimeRoot, USER_RUNTIME_MAX_AGE_MS }
|
|
27
29
|
|
|
@@ -267,38 +269,6 @@ function buildInitialStateSnapshot({
|
|
|
267
269
|
].join('\n')
|
|
268
270
|
}
|
|
269
271
|
|
|
270
|
-
function normalizeProjectSessionState(scope) {
|
|
271
|
-
if (!scope?.statePath) return
|
|
272
|
-
|
|
273
|
-
const currentDocument = readStateDocument(scope.statePath)
|
|
274
|
-
if (currentDocument.hasMetadata) {
|
|
275
|
-
if (currentDocument.metadata && typeof currentDocument.metadata === 'object' && !existsSync(scope.runtimePath)) {
|
|
276
|
-
writeJsonFileAtomic(scope.runtimePath, currentDocument.metadata)
|
|
277
|
-
}
|
|
278
|
-
writeStateDocument(scope.statePath, {
|
|
279
|
-
body: currentDocument.body,
|
|
280
|
-
})
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const workspaceStatePath = scope.workspaceDir ? join(scope.workspaceDir, 'STATE.md') : ''
|
|
284
|
-
if (!workspaceStatePath || samePath(workspaceStatePath, scope.statePath) || !existsSync(workspaceStatePath)) return
|
|
285
|
-
|
|
286
|
-
const legacyDocument = readStateDocument(workspaceStatePath)
|
|
287
|
-
if (!existsSync(scope.statePath) && legacyDocument.body.trim()) {
|
|
288
|
-
writeStateDocument(scope.statePath, {
|
|
289
|
-
body: legacyDocument.body,
|
|
290
|
-
})
|
|
291
|
-
}
|
|
292
|
-
if (legacyDocument.metadata && typeof legacyDocument.metadata === 'object' && !existsSync(scope.runtimePath)) {
|
|
293
|
-
writeJsonFileAtomic(scope.runtimePath, legacyDocument.metadata)
|
|
294
|
-
}
|
|
295
|
-
if (legacyDocument.hasMetadata) {
|
|
296
|
-
writeStateDocument(workspaceStatePath, {
|
|
297
|
-
body: legacyDocument.body,
|
|
298
|
-
})
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
272
|
export function ensureProjectLocalRuntime(cwd, options = {}) {
|
|
303
273
|
const normalizedCwd = normalizePath(cwd || process.cwd())
|
|
304
274
|
const localDir = getProjectLocalDir(normalizedCwd)
|
|
@@ -310,7 +280,6 @@ export function ensureProjectLocalRuntime(cwd, options = {}) {
|
|
|
310
280
|
if (!existsSync(scope.statePath)) {
|
|
311
281
|
writeFileSync(scope.statePath, `${buildInitialStateSnapshot(options.stateSeed || {})}\n`, 'utf-8')
|
|
312
282
|
}
|
|
313
|
-
normalizeProjectSessionState(scope)
|
|
314
283
|
|
|
315
284
|
return scope
|
|
316
285
|
}
|
|
@@ -369,6 +338,31 @@ function resolvePayloadSessionToken(payload = {}) {
|
|
|
369
338
|
})
|
|
370
339
|
}
|
|
371
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
|
+
|
|
372
366
|
function resolveEnvSessionToken(env = process.env) {
|
|
373
367
|
return resolveProjectSessionToken({ payload: {}, env })
|
|
374
368
|
}
|
|
@@ -377,6 +371,17 @@ function resolveEnvSessionAliasToken(env = process.env) {
|
|
|
377
371
|
return resolveProjectSessionAliasToken({ env })
|
|
378
372
|
}
|
|
379
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
|
+
|
|
380
385
|
function resolveTransientSessionToken({ payload = {}, env = process.env, ppid = process.ppid } = {}) {
|
|
381
386
|
return resolveSessionToken({
|
|
382
387
|
payload,
|
|
@@ -393,18 +398,33 @@ function buildScopedSessionToken(kind = '', raw = '') {
|
|
|
393
398
|
return `${normalizedKind}-${value}`
|
|
394
399
|
}
|
|
395
400
|
|
|
396
|
-
function
|
|
397
|
-
|
|
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))]
|
|
398
421
|
}
|
|
399
422
|
|
|
400
|
-
function
|
|
401
|
-
|
|
402
|
-
try {
|
|
403
|
-
rmSync(join(activationDir, PROJECT_SESSIONS_DIR_NAME, LEGACY_SESSION_POINTERS_FILE_NAME), { force: true })
|
|
404
|
-
} catch {}
|
|
423
|
+
function getActiveSessionPath(activationDir) {
|
|
424
|
+
return join(activationDir, PROJECT_SESSIONS_DIR_NAME, ACTIVE_SESSION_FILE_NAME)
|
|
405
425
|
}
|
|
406
426
|
|
|
407
|
-
function
|
|
427
|
+
function readActiveProjectSession({ activationDir, projectRoot, workspace, now = Date.now() } = {}) {
|
|
408
428
|
const active = readJsonFile(getActiveSessionPath(activationDir), null)
|
|
409
429
|
if (!active || typeof active !== 'object') return ''
|
|
410
430
|
if (active.cwd && !samePath(active.cwd, projectRoot)) return ''
|
|
@@ -415,43 +435,77 @@ function resolveActiveSessionToken({ activationDir, projectRoot, workspace, now
|
|
|
415
435
|
const updatedAt = Date.parse(active.updatedAt || '')
|
|
416
436
|
if (!Number.isFinite(updatedAt) || now - updatedAt > USER_RUNTIME_MAX_AGE_MS) return ''
|
|
417
437
|
|
|
418
|
-
return
|
|
438
|
+
return active
|
|
419
439
|
}
|
|
420
440
|
|
|
421
441
|
function resolveActiveAliasSession({ activationDir, projectRoot, workspace, alias, now = Date.now() } = {}) {
|
|
422
442
|
if (!alias) return ''
|
|
423
|
-
const active =
|
|
443
|
+
const active = readActiveProjectSession({
|
|
444
|
+
activationDir,
|
|
445
|
+
projectRoot,
|
|
446
|
+
workspace,
|
|
447
|
+
now,
|
|
448
|
+
})
|
|
424
449
|
if (!active || typeof active !== 'object') return ''
|
|
425
|
-
if (active.cwd && !samePath(active.cwd, projectRoot)) return ''
|
|
426
450
|
|
|
427
|
-
const
|
|
428
|
-
|
|
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
|
+
}
|
|
429
462
|
|
|
430
|
-
|
|
431
|
-
|
|
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
|
+
}
|
|
432
470
|
|
|
433
|
-
|
|
434
|
-
|
|
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
|
+
: ''
|
|
435
481
|
}
|
|
436
482
|
|
|
437
|
-
export function writeActiveProjectSession(scope, { host = '', source = '', env = process.env } = {}) {
|
|
483
|
+
export function writeActiveProjectSession(scope, { host = '', source = '', payload = {}, env = process.env, ppid = process.ppid } = {}) {
|
|
438
484
|
if (!scope?.active || !scope.activationDir || !scope.workspace) return ''
|
|
439
485
|
|
|
440
486
|
const activePath = getActiveSessionPath(scope.activationDir)
|
|
441
487
|
const current = readJsonFile(activePath, null) || {}
|
|
442
|
-
const aliases = current.aliases && typeof current.aliases === 'object' ? current.aliases : {}
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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
|
+
}
|
|
447
500
|
writeJsonFileAtomic(activePath, {
|
|
448
501
|
version: 1,
|
|
449
502
|
cwd: scope.cwd,
|
|
450
503
|
workspace: scope.workspace || scope.branch,
|
|
451
|
-
session
|
|
452
|
-
sessionMode
|
|
504
|
+
session,
|
|
505
|
+
sessionMode,
|
|
453
506
|
host,
|
|
454
507
|
source,
|
|
508
|
+
...(hostHint ? { hostHint } : {}),
|
|
455
509
|
aliases,
|
|
456
510
|
...(current.cleanupCheckedAt ? { cleanupCheckedAt: current.cleanupCheckedAt } : {}),
|
|
457
511
|
updatedAt: new Date().toISOString(),
|
|
@@ -459,23 +513,73 @@ export function writeActiveProjectSession(scope, { host = '', source = '', env =
|
|
|
459
513
|
return activePath
|
|
460
514
|
}
|
|
461
515
|
|
|
462
|
-
function chooseProjectSession({ payload, env, activationDir, projectRoot, workspace }) {
|
|
463
|
-
const
|
|
464
|
-
const
|
|
516
|
+
function chooseProjectSession({ payload, env, ppid, activationDir, projectRoot, workspace }) {
|
|
517
|
+
const workspaceDir = join(activationDir, PROJECT_SESSIONS_DIR_NAME, workspace)
|
|
518
|
+
const active = readActiveProjectSession({
|
|
519
|
+
activationDir,
|
|
520
|
+
projectRoot,
|
|
521
|
+
workspace,
|
|
522
|
+
})
|
|
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
|
+
}
|
|
543
|
+
|
|
544
|
+
const envToken = sanitizeRuntimeSegment(resolveEnvSessionToken(env), '')
|
|
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
|
+
}
|
|
576
|
+
|
|
465
577
|
if (payloadToken) {
|
|
466
578
|
return {
|
|
467
579
|
session: buildScopedSessionToken('host', payloadToken),
|
|
468
580
|
sessionMode: 'host-session',
|
|
469
581
|
}
|
|
470
582
|
}
|
|
471
|
-
|
|
472
|
-
const payloadAliasToken = resolveActiveAliasSession({
|
|
473
|
-
activationDir,
|
|
474
|
-
projectRoot,
|
|
475
|
-
workspace,
|
|
476
|
-
alias: payloadAlias,
|
|
477
|
-
})
|
|
478
|
-
if (payloadAliasToken) return { session: payloadAliasToken, sessionMode: 'active-session' }
|
|
479
583
|
if (payloadAlias) {
|
|
480
584
|
return {
|
|
481
585
|
session: buildScopedSessionToken('alias', payloadAlias),
|
|
@@ -483,16 +587,6 @@ function chooseProjectSession({ payload, env, activationDir, projectRoot, worksp
|
|
|
483
587
|
}
|
|
484
588
|
}
|
|
485
589
|
|
|
486
|
-
const envToken = sanitizeRuntimeSegment(resolveEnvSessionToken(env), '')
|
|
487
|
-
const envAliasToken = sanitizeRuntimeSegment(resolveEnvSessionAliasToken(env), '')
|
|
488
|
-
const aliasToken = resolveActiveAliasSession({
|
|
489
|
-
activationDir,
|
|
490
|
-
projectRoot,
|
|
491
|
-
workspace,
|
|
492
|
-
alias: envToken,
|
|
493
|
-
})
|
|
494
|
-
if (aliasToken) return { session: aliasToken, sessionMode: 'active-session' }
|
|
495
|
-
|
|
496
590
|
if (envToken) {
|
|
497
591
|
return {
|
|
498
592
|
session: buildScopedSessionToken('host', envToken),
|
|
@@ -501,42 +595,33 @@ function chooseProjectSession({ payload, env, activationDir, projectRoot, worksp
|
|
|
501
595
|
}
|
|
502
596
|
|
|
503
597
|
if (envAliasToken) {
|
|
504
|
-
const activeAliasToken = resolveActiveAliasSession({
|
|
505
|
-
activationDir,
|
|
506
|
-
projectRoot,
|
|
507
|
-
workspace,
|
|
508
|
-
alias: envAliasToken,
|
|
509
|
-
})
|
|
510
|
-
if (activeAliasToken) return { session: activeAliasToken, sessionMode: 'active-session' }
|
|
511
598
|
return {
|
|
512
599
|
session: buildScopedSessionToken('alias', envAliasToken),
|
|
513
600
|
sessionMode: 'alias-session',
|
|
514
601
|
}
|
|
515
602
|
}
|
|
516
603
|
|
|
517
|
-
|
|
518
|
-
|
|
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
|
+
}
|
|
519
611
|
|
|
520
|
-
|
|
521
|
-
if (!activationDir) return
|
|
522
|
-
const artifactsDir = join(activationDir, PROJECT_ARTIFACTS_DIR_NAME)
|
|
523
|
-
if (!existsSync(artifactsDir)) return
|
|
524
|
-
try {
|
|
525
|
-
rmSync(artifactsDir, { recursive: true, force: true })
|
|
526
|
-
} catch {}
|
|
612
|
+
return { session: '', sessionMode: 'unidentified' }
|
|
527
613
|
}
|
|
528
614
|
|
|
529
615
|
export function getProjectSessionScope(cwd, options = {}) {
|
|
530
616
|
const normalizedCwd = normalizePath(cwd || process.cwd())
|
|
531
617
|
const projectRoot = getProjectRoot(normalizedCwd)
|
|
532
|
-
const { payload = {}, env = process.env } = normalizeRuntimeOptions(options)
|
|
618
|
+
const { payload = {}, env = process.env, ppid = process.ppid } = normalizeRuntimeOptions(options)
|
|
533
619
|
const activationDir = getProjectActivationDir(projectRoot)
|
|
534
|
-
removeLegacyProjectArtifacts(activationDir)
|
|
535
|
-
removeLegacySessionPointersFile(activationDir)
|
|
536
620
|
const workspace = resolveWorkspaceName(projectRoot)
|
|
537
621
|
const { session, sessionMode } = chooseProjectSession({
|
|
538
622
|
payload,
|
|
539
623
|
env,
|
|
624
|
+
ppid,
|
|
540
625
|
activationDir,
|
|
541
626
|
projectRoot,
|
|
542
627
|
workspace,
|