@lumenflow/cli 5.5.0 → 5.7.14

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.
Files changed (229) hide show
  1. package/README.md +41 -40
  2. package/dist/db-journal-recover.js +400 -0
  3. package/dist/db-journal-recover.js.map +1 -0
  4. package/dist/docs-sync.js +8 -3
  5. package/dist/docs-sync.js.map +1 -1
  6. package/dist/doctor.js +11 -0
  7. package/dist/doctor.js.map +1 -1
  8. package/dist/gate-defaults.js +37 -0
  9. package/dist/gate-defaults.js.map +1 -1
  10. package/dist/gates/monolithic-file-contention-guard.js +167 -0
  11. package/dist/gates/monolithic-file-contention-guard.js.map +1 -0
  12. package/dist/gates/prod-migration-drift.js +207 -0
  13. package/dist/gates/prod-migration-drift.js.map +1 -0
  14. package/dist/gates/test-over-deletion-guard.js +280 -0
  15. package/dist/gates/test-over-deletion-guard.js.map +1 -0
  16. package/dist/gates-runners.js +44 -3
  17. package/dist/gates-runners.js.map +1 -1
  18. package/dist/gates.js +3 -2
  19. package/dist/gates.js.map +1 -1
  20. package/dist/hooks/config-resolver.js +16 -1
  21. package/dist/hooks/config-resolver.js.map +1 -1
  22. package/dist/hooks/dirty-guard.js +43 -2
  23. package/dist/hooks/dirty-guard.js.map +1 -1
  24. package/dist/hooks/git-status-parser.js +22 -8
  25. package/dist/hooks/git-status-parser.js.map +1 -1
  26. package/dist/init-templates.js +241 -0
  27. package/dist/init-templates.js.map +1 -1
  28. package/dist/init.js +122 -16
  29. package/dist/init.js.map +1 -1
  30. package/dist/lumenflow-setup.js +144 -0
  31. package/dist/lumenflow-setup.js.map +1 -0
  32. package/dist/lumenflow-upgrade.js +43 -1
  33. package/dist/lumenflow-upgrade.js.map +1 -1
  34. package/dist/mem-create.js +10 -1
  35. package/dist/mem-create.js.map +1 -1
  36. package/dist/mem-signal.js +21 -4
  37. package/dist/mem-signal.js.map +1 -1
  38. package/dist/orchestrate-initiative.js +28 -3
  39. package/dist/orchestrate-initiative.js.map +1 -1
  40. package/dist/public-manifest.js +17 -7
  41. package/dist/public-manifest.js.map +1 -1
  42. package/dist/release.js +53 -18
  43. package/dist/release.js.map +1 -1
  44. package/dist/wu-done-gates.js +13 -9
  45. package/dist/wu-done-gates.js.map +1 -1
  46. package/dist/wu-done.js +14 -2
  47. package/dist/wu-done.js.map +1 -1
  48. package/dist/wu-edit-operations.js +74 -0
  49. package/dist/wu-edit-operations.js.map +1 -1
  50. package/dist/wu-edit-validators.js +58 -0
  51. package/dist/wu-edit-validators.js.map +1 -1
  52. package/dist/wu-edit.js +106 -4
  53. package/dist/wu-edit.js.map +1 -1
  54. package/dist/wu-prep.js +57 -9
  55. package/dist/wu-prep.js.map +1 -1
  56. package/dist/wu-recover.js +6 -0
  57. package/dist/wu-recover.js.map +1 -1
  58. package/dist/wu-release.js +120 -2
  59. package/dist/wu-release.js.map +1 -1
  60. package/dist/wu-sizing-validation.js +47 -17
  61. package/dist/wu-sizing-validation.js.map +1 -1
  62. package/dist/wu-status.js +33 -0
  63. package/dist/wu-status.js.map +1 -1
  64. package/package.json +13 -12
  65. package/packs/agent-runtime/package.json +1 -1
  66. package/packs/sidekick/package.json +1 -1
  67. package/packs/software-delivery/package.json +1 -1
  68. package/templates/core/AGENTS.md.template +67 -3
  69. package/templates/core/LUMENFLOW.md.template +196 -47
  70. package/dist/distribution-preflight.js +0 -230
  71. package/dist/distribution-preflight.js.map +0 -1
  72. package/packs/agent-runtime/agent-heartbeat.ts +0 -163
  73. package/packs/agent-runtime/auto-session-integration.ts +0 -888
  74. package/packs/agent-runtime/capability-factory.ts +0 -104
  75. package/packs/agent-runtime/constants.ts +0 -21
  76. package/packs/agent-runtime/delegation-registry-schema.ts +0 -220
  77. package/packs/agent-runtime/delegation-registry-store.ts +0 -269
  78. package/packs/agent-runtime/delegation-tree.ts +0 -328
  79. package/packs/agent-runtime/index.ts +0 -20
  80. package/packs/agent-runtime/manifest.ts +0 -348
  81. package/packs/agent-runtime/memory-coordination-contract.ts +0 -86
  82. package/packs/agent-runtime/orchestration.ts +0 -2027
  83. package/packs/agent-runtime/pack-registration.ts +0 -110
  84. package/packs/agent-runtime/policy-factory.ts +0 -165
  85. package/packs/agent-runtime/remote-controls/index.ts +0 -7
  86. package/packs/agent-runtime/remote-controls/operations.ts +0 -405
  87. package/packs/agent-runtime/remote-controls/port.ts +0 -48
  88. package/packs/agent-runtime/remote-controls/state-store.ts +0 -258
  89. package/packs/agent-runtime/remote-controls/types.ts +0 -105
  90. package/packs/agent-runtime/session-schema.ts +0 -467
  91. package/packs/agent-runtime/tool-impl/agent-turn-tools.ts +0 -793
  92. package/packs/agent-runtime/tool-impl/index.ts +0 -6
  93. package/packs/agent-runtime/tool-impl/provider-adapters.ts +0 -1245
  94. package/packs/agent-runtime/tool-impl/remote-controls.mock.ts +0 -256
  95. package/packs/agent-runtime/tool-impl/remote-controls.ts +0 -273
  96. package/packs/agent-runtime/tools/index.ts +0 -4
  97. package/packs/agent-runtime/tools/types.ts +0 -47
  98. package/packs/agent-runtime/turn-lifecycle-events.ts +0 -590
  99. package/packs/agent-runtime/types.ts +0 -128
  100. package/packs/agent-runtime/vitest.config.ts +0 -11
  101. package/packs/sidekick/channel-ingress.ts +0 -137
  102. package/packs/sidekick/constants.ts +0 -10
  103. package/packs/sidekick/index.ts +0 -8
  104. package/packs/sidekick/manifest-schema.ts +0 -49
  105. package/packs/sidekick/manifest.ts +0 -512
  106. package/packs/sidekick/pack-registration.ts +0 -110
  107. package/packs/sidekick/policy-factory.ts +0 -38
  108. package/packs/sidekick/sidekick-events.ts +0 -694
  109. package/packs/sidekick/src/adapters/cloud-queue.ts +0 -101
  110. package/packs/sidekick/src/adapters/control-plane-bridge.adapter.ts +0 -386
  111. package/packs/sidekick/src/adapters/filesystem-bridge.adapter.ts +0 -228
  112. package/packs/sidekick/src/domain/channel.types.ts +0 -64
  113. package/packs/sidekick/src/ports/channel-bridge.port.ts +0 -92
  114. package/packs/sidekick/src/routines/commit.ts +0 -74
  115. package/packs/sidekick/tool-impl/channel-tools.ts +0 -577
  116. package/packs/sidekick/tool-impl/channel-transports.ts +0 -75
  117. package/packs/sidekick/tool-impl/index.ts +0 -29
  118. package/packs/sidekick/tool-impl/memory-tools.ts +0 -290
  119. package/packs/sidekick/tool-impl/routine-commit.ts +0 -102
  120. package/packs/sidekick/tool-impl/routine-tools.ts +0 -440
  121. package/packs/sidekick/tool-impl/runtime-context.ts +0 -28
  122. package/packs/sidekick/tool-impl/shared.ts +0 -125
  123. package/packs/sidekick/tool-impl/storage.ts +0 -325
  124. package/packs/sidekick/tool-impl/system-tools.ts +0 -160
  125. package/packs/sidekick/tool-impl/task-tools.ts +0 -506
  126. package/packs/sidekick/tools/channel-tools.ts +0 -53
  127. package/packs/sidekick/tools/index.ts +0 -9
  128. package/packs/sidekick/tools/memory-tools.ts +0 -53
  129. package/packs/sidekick/tools/routine-tools.ts +0 -53
  130. package/packs/sidekick/tools/system-tools.ts +0 -47
  131. package/packs/sidekick/tools/task-tools.ts +0 -61
  132. package/packs/sidekick/tools/types.ts +0 -57
  133. package/packs/sidekick/vitest.config.ts +0 -11
  134. package/packs/software-delivery/constants.ts +0 -10
  135. package/packs/software-delivery/extensions.ts +0 -140
  136. package/packs/software-delivery/gate-policies.ts +0 -134
  137. package/packs/software-delivery/index.ts +0 -8
  138. package/packs/software-delivery/manifest-schema.ts +0 -268
  139. package/packs/software-delivery/manifest.ts +0 -657
  140. package/packs/software-delivery/pack-registration.ts +0 -113
  141. package/packs/software-delivery/src/commands/index.ts +0 -5
  142. package/packs/software-delivery/src/config/delivery-review-contract.ts +0 -256
  143. package/packs/software-delivery/src/config/env-accessors.ts +0 -66
  144. package/packs/software-delivery/src/config/index.ts +0 -8
  145. package/packs/software-delivery/src/config/normalize-config-keys.ts +0 -9
  146. package/packs/software-delivery/src/config/schemas/lumenflow-config-schema-types.ts +0 -460
  147. package/packs/software-delivery/src/config/workspace-reader.ts +0 -375
  148. package/packs/software-delivery/src/constants/backlog-patterns.ts +0 -31
  149. package/packs/software-delivery/src/constants/client-ids.ts +0 -19
  150. package/packs/software-delivery/src/constants/config-contract.ts +0 -7
  151. package/packs/software-delivery/src/constants/docs-layout-presets.ts +0 -50
  152. package/packs/software-delivery/src/constants/duration-constants.ts +0 -20
  153. package/packs/software-delivery/src/constants/gate-constants.ts +0 -32
  154. package/packs/software-delivery/src/constants/index.ts +0 -29
  155. package/packs/software-delivery/src/constants/lock-constants.ts +0 -35
  156. package/packs/software-delivery/src/constants/object-guards.ts +0 -12
  157. package/packs/software-delivery/src/constants/section-headings.ts +0 -107
  158. package/packs/software-delivery/src/constants/wu-cli-constants.ts +0 -500
  159. package/packs/software-delivery/src/constants/wu-domain-constants.ts +0 -466
  160. package/packs/software-delivery/src/constants/wu-git-constants.ts +0 -7
  161. package/packs/software-delivery/src/constants/wu-id-format.ts +0 -327
  162. package/packs/software-delivery/src/constants/wu-paths-constants.ts +0 -384
  163. package/packs/software-delivery/src/constants/wu-statuses.ts +0 -287
  164. package/packs/software-delivery/src/constants/wu-type-helpers.ts +0 -67
  165. package/packs/software-delivery/src/constants/wu-ui-constants.ts +0 -267
  166. package/packs/software-delivery/src/constants/wu-validation-constants.ts +0 -73
  167. package/packs/software-delivery/src/domain/index.ts +0 -5
  168. package/packs/software-delivery/src/domain/orchestration.constants.ts +0 -166
  169. package/packs/software-delivery/src/domain/orchestration.schemas.ts +0 -238
  170. package/packs/software-delivery/src/domain/orchestration.types.ts +0 -176
  171. package/packs/software-delivery/src/methodology/incremental-test.ts +0 -122
  172. package/packs/software-delivery/src/methodology/index.ts +0 -6
  173. package/packs/software-delivery/src/methodology/manual-test-validator.ts +0 -292
  174. package/packs/software-delivery/src/policy/coverage-gate.ts +0 -270
  175. package/packs/software-delivery/src/policy/gates-agent-mode.ts +0 -223
  176. package/packs/software-delivery/src/policy/gates-config-internal.ts +0 -121
  177. package/packs/software-delivery/src/policy/gates-config.ts +0 -300
  178. package/packs/software-delivery/src/policy/gates-coverage.ts +0 -356
  179. package/packs/software-delivery/src/policy/gates-presets.ts +0 -134
  180. package/packs/software-delivery/src/policy/gates-schemas.ts +0 -173
  181. package/packs/software-delivery/src/policy/index.ts +0 -22
  182. package/packs/software-delivery/src/policy/package-manager-resolver.ts +0 -319
  183. package/packs/software-delivery/src/policy/resolve-policy.ts +0 -601
  184. package/packs/software-delivery/src/ports/config.ports.ts +0 -90
  185. package/packs/software-delivery/src/ports/dashboard-renderer.port.ts +0 -125
  186. package/packs/software-delivery/src/ports/index.ts +0 -10
  187. package/packs/software-delivery/src/ports/sync-validator.ports.ts +0 -59
  188. package/packs/software-delivery/src/ports/wu-helpers.ports.ts +0 -168
  189. package/packs/software-delivery/src/ports/wu-state.ports.ts +0 -241
  190. package/packs/software-delivery/src/primitives/index.ts +0 -5
  191. package/packs/software-delivery/src/runtime/index.ts +0 -6
  192. package/packs/software-delivery/src/runtime/work-classifier.ts +0 -561
  193. package/packs/software-delivery/src/sandbox/index.ts +0 -10
  194. package/packs/software-delivery/src/sandbox/sandbox-allowlist.ts +0 -118
  195. package/packs/software-delivery/src/sandbox/sandbox-backend-linux.ts +0 -88
  196. package/packs/software-delivery/src/sandbox/sandbox-backend-macos.ts +0 -154
  197. package/packs/software-delivery/src/sandbox/sandbox-backend-windows.ts +0 -47
  198. package/packs/software-delivery/src/sandbox/sandbox-profile.ts +0 -153
  199. package/packs/software-delivery/src/schemas/index.ts +0 -5
  200. package/packs/software-delivery/src/state/date-utils.ts +0 -158
  201. package/packs/software-delivery/src/state/index.ts +0 -15
  202. package/packs/software-delivery/src/state/state-machine.ts +0 -119
  203. package/packs/software-delivery/src/state/wu-doc-types.ts +0 -51
  204. package/packs/software-delivery/src/state/wu-paths.ts +0 -381
  205. package/packs/software-delivery/src/state/wu-schema.ts +0 -1139
  206. package/packs/software-delivery/src/state/wu-state-schema.ts +0 -255
  207. package/packs/software-delivery/src/state/wu-yaml.ts +0 -338
  208. package/packs/software-delivery/tool-impl/agent-tools.ts +0 -263
  209. package/packs/software-delivery/tool-impl/delegation-tools.ts +0 -66
  210. package/packs/software-delivery/tool-impl/flow-metrics-tools.ts +0 -219
  211. package/packs/software-delivery/tool-impl/git-runner.ts +0 -113
  212. package/packs/software-delivery/tool-impl/git-tools.ts +0 -316
  213. package/packs/software-delivery/tool-impl/index.ts +0 -15
  214. package/packs/software-delivery/tool-impl/initiative-orchestration-tools.ts +0 -720
  215. package/packs/software-delivery/tool-impl/lane-lock.ts +0 -246
  216. package/packs/software-delivery/tool-impl/memory-tools.ts +0 -470
  217. package/packs/software-delivery/tool-impl/pending-runtime-tools.ts +0 -21
  218. package/packs/software-delivery/tool-impl/runtime-cli-adapter.ts +0 -329
  219. package/packs/software-delivery/tool-impl/runtime-native-tools.ts +0 -687
  220. package/packs/software-delivery/tool-impl/worker-loader.ts +0 -52
  221. package/packs/software-delivery/tool-impl/worktree-tools.ts +0 -46
  222. package/packs/software-delivery/tool-impl/wu-lifecycle-tools.ts +0 -807
  223. package/packs/software-delivery/tools/delegation-tools.ts +0 -23
  224. package/packs/software-delivery/tools/git-tools.ts +0 -55
  225. package/packs/software-delivery/tools/index.ts +0 -8
  226. package/packs/software-delivery/tools/lane-lock-tool.ts +0 -37
  227. package/packs/software-delivery/tools/types.ts +0 -71
  228. package/packs/software-delivery/tools/worktree-tools.ts +0 -49
  229. package/packs/software-delivery/vitest.config.ts +0 -11
@@ -1,888 +0,0 @@
1
- // Copyright (c) 2026 Hellmai Ltd
2
- // SPDX-License-Identifier: LicenseRef-LumenFlow-Proprietary
3
-
4
- /**
5
- * Auto-Session Integration for wu:claim and wu:done lifecycle (WU-1438, WU-1466)
6
- *
7
- * Provides wrapper functions around agent-session.ts that:
8
- * 1. Auto-start sessions on wu:claim with silent no-op if already active
9
- * 2. Auto-end sessions on wu:done with silent no-op if not active
10
- * 3. Store session_id in WU YAML for tracking
11
- * 4. Create memory layer session nodes for context restoration (WU-1466)
12
- *
13
- * Design principles:
14
- * - Composition over modification (wraps existing agent-session.ts)
15
- * - Silent failures for idempotent operations (no throw on duplicate start/end)
16
- * - Configurable session directory for testing
17
- */
18
- import { randomUUID } from 'crypto';
19
- import { readFileSync, writeFileSync, existsSync, unlinkSync, mkdirSync, readdirSync } from 'fs';
20
- import { join } from 'path';
21
- import { parse as parseYAML } from 'yaml';
22
- import {
23
- HeartbeatManager,
24
- type AgentHeartbeatInput,
25
- type AgentHeartbeatResult,
26
- type HeartbeatHealth,
27
- } from './agent-heartbeat.js';
28
- import {
29
- acquireDisplayName,
30
- releaseDisplayName,
31
- SESSION_SCHEMA_VERSION_V2,
32
- withV2RoleDefaults,
33
- type SessionSchemaVersion,
34
- } from './session-schema.js';
35
-
36
- const SESSION_FILENAME = 'current.json';
37
- const SESSION_DIR = '.lumenflow/sessions';
38
- const WORKSPACE_CONFIG_FILE = 'workspace.yaml';
39
- const CONTROL_PLANE_REGISTER_PATH = '/api/v1/sessions/register';
40
- const CONTROL_PLANE_DEREGISTER_PATH = '/api/v1/sessions/deregister';
41
- const CONTROL_PLANE_HEARTBEAT_PATH = '/api/v1/heartbeat';
42
-
43
- // Default context tier for auto-started sessions
44
- const DEFAULT_TIER: 1 | 2 | 3 = 2;
45
-
46
- // Agent type for auto-started sessions
47
- const DEFAULT_AGENT_TYPE = 'claude-code';
48
- const DEFAULT_AGENT_VERSION = 'unknown';
49
- const DEFAULT_HOST_ID = 'unknown';
50
- const DEFAULT_AGENT_CAPABILITIES = ['session_lifecycle', 'heartbeat'] as const;
51
- const AGENT_CAPABILITIES_ENV = 'LUMENFLOW_AGENT_CAPABILITIES';
52
- const AGENT_VERSION_ENV = 'LUMENFLOW_AGENT_VERSION';
53
-
54
- type SessionMetadataValue = string | number | boolean | string[];
55
-
56
- /**
57
- * Session data stored in current.json
58
- *
59
- * WU-2754 (ADR-014 extension 2): `schemaVersion`, `lifecycle_role`,
60
- * `specialty_profile`, and `delegation_id` are optional additive fields.
61
- * v1 legacy records (no `schemaVersion`) continue to load without them.
62
- */
63
- interface SessionFileData {
64
- session_id: string;
65
- wu_id: string;
66
- started: string;
67
- last_heartbeat?: string;
68
- completed?: string;
69
- lane?: string;
70
- agent_type: string;
71
- client_type?: string;
72
- capabilities?: string[];
73
- agent_version?: string;
74
- host_id?: string;
75
- context_tier: number;
76
- incidents_logged: number;
77
- incidents_major: number;
78
- auto_started?: boolean;
79
- /** ADR-014 extension 2 — schema version stamp for v2 records (WU-2754). */
80
- schemaVersion?: SessionSchemaVersion;
81
- /** ADR-014 extension 2 — what phase of delivery this session owns (WU-2754). */
82
- lifecycle_role?: string;
83
- /** ADR-014 extension 2 — which skill bundle the agent identifies as carrying (WU-2754). */
84
- specialty_profile?: string;
85
- display_name?: string;
86
- /** Stable explicit A2A reader identity. Format hint: <owner>:<role>:<purpose>. */
87
- agent_identity?: string;
88
- /** Reserved cloud workspace/tenant scope slot; unused by local runtime. */
89
- tenant_id?: string;
90
- /** ADR-014 extension 2 — parent delegation registry reference (WU-2754). */
91
- delegation_id?: string;
92
- }
93
-
94
- interface RegisterSessionInput {
95
- workspace_id: string;
96
- session_id: string;
97
- agent_id: string;
98
- started_at: string;
99
- lane?: string;
100
- wu_id?: string;
101
- client_type?: string;
102
- capabilities?: string[];
103
- agent_version?: string;
104
- host_id?: string;
105
- metadata?: Record<string, SessionMetadataValue>;
106
- }
107
-
108
- interface DeregisterSessionInput {
109
- workspace_id: string;
110
- session_id: string;
111
- ended_at?: string;
112
- reason?: string;
113
- }
114
-
115
- interface ControlPlaneSessionSyncPort {
116
- registerSession(input: RegisterSessionInput): Promise<unknown>;
117
- deregisterSession(input: DeregisterSessionInput): Promise<unknown>;
118
- heartbeat?(input: AgentHeartbeatInput): Promise<AgentHeartbeatResult>;
119
- }
120
-
121
- interface ResolvedControlPlaneSyncConfig {
122
- workspaceId: string;
123
- endpoint: string;
124
- token: string;
125
- }
126
-
127
- interface WorkspaceControlPlaneDocument {
128
- id?: unknown;
129
- control_plane?: {
130
- endpoint?: unknown;
131
- auth?: {
132
- token_env?: unknown;
133
- };
134
- };
135
- }
136
-
137
- function parseCapabilities(rawValue: string | null): string[] {
138
- if (!rawValue) {
139
- return [...DEFAULT_AGENT_CAPABILITIES];
140
- }
141
-
142
- const parsed = rawValue
143
- .split(',')
144
- .map((entry) => entry.trim())
145
- .filter((entry) => entry.length > 0);
146
- return parsed.length > 0 ? parsed : [...DEFAULT_AGENT_CAPABILITIES];
147
- }
148
-
149
- function asNonEmptyString(value: unknown): string | null {
150
- return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
151
- }
152
-
153
- function normalizeEndpoint(endpoint: string): string {
154
- return endpoint.endsWith('/') ? endpoint.slice(0, endpoint.length - 1) : endpoint;
155
- }
156
-
157
- function resolveControlPlaneSessionSyncConfig(
158
- workspaceRoot: string,
159
- environment: NodeJS.ProcessEnv,
160
- ): ResolvedControlPlaneSyncConfig | null {
161
- const workspacePath = join(workspaceRoot, WORKSPACE_CONFIG_FILE);
162
- if (!existsSync(workspacePath)) {
163
- return null;
164
- }
165
-
166
- let parsedWorkspace: WorkspaceControlPlaneDocument | null;
167
- try {
168
- parsedWorkspace = parseYAML(
169
- readFileSync(workspacePath, { encoding: 'utf-8' }),
170
- ) as WorkspaceControlPlaneDocument | null;
171
- } catch {
172
- return null;
173
- }
174
-
175
- const workspaceId = asNonEmptyString(parsedWorkspace?.id);
176
- const endpoint = asNonEmptyString(parsedWorkspace?.control_plane?.endpoint);
177
- const tokenEnv = asNonEmptyString(parsedWorkspace?.control_plane?.auth?.token_env);
178
- if (!workspaceId || !endpoint || !tokenEnv) {
179
- return null;
180
- }
181
-
182
- const token = asNonEmptyString(environment[tokenEnv]);
183
- if (!token) {
184
- return null;
185
- }
186
-
187
- try {
188
- // Validate endpoint before creating outbound requests.
189
- void new URL(endpoint);
190
- } catch {
191
- return null;
192
- }
193
-
194
- return {
195
- workspaceId,
196
- endpoint: normalizeEndpoint(endpoint),
197
- token,
198
- };
199
- }
200
-
201
- function resolveStandardSessionMetadata(input: {
202
- agentType: string;
203
- environment: NodeJS.ProcessEnv;
204
- }): {
205
- client_type: string;
206
- capabilities: string[];
207
- agent_version: string;
208
- host_id: string;
209
- metadata: Record<string, SessionMetadataValue>;
210
- } {
211
- const clientType = input.agentType;
212
- const capabilities = parseCapabilities(
213
- asNonEmptyString(input.environment[AGENT_CAPABILITIES_ENV]),
214
- );
215
- const agentVersion =
216
- asNonEmptyString(input.environment[AGENT_VERSION_ENV]) ?? DEFAULT_AGENT_VERSION;
217
- const hostId =
218
- asNonEmptyString(input.environment.HOSTNAME) ??
219
- asNonEmptyString(input.environment.COMPUTERNAME) ??
220
- DEFAULT_HOST_ID;
221
-
222
- return {
223
- client_type: clientType,
224
- capabilities,
225
- agent_version: agentVersion,
226
- host_id: hostId,
227
- metadata: {
228
- client_type: clientType,
229
- capabilities,
230
- agent_version: agentVersion,
231
- host_id: hostId,
232
- },
233
- };
234
- }
235
-
236
- async function postControlPlaneSessionHook(
237
- endpoint: string,
238
- token: string,
239
- path: string,
240
- payload: object,
241
- fetchFn: typeof fetch,
242
- ): Promise<void> {
243
- const response = await fetchFn(`${endpoint}${path}`, {
244
- method: 'POST',
245
- headers: {
246
- authorization: `Bearer ${token}`,
247
- 'content-type': 'application/json',
248
- },
249
- body: JSON.stringify(payload),
250
- });
251
-
252
- if (!response.ok) {
253
- throw new Error(`HTTP ${response.status}`);
254
- }
255
- }
256
-
257
- function asFiniteNumber(value: unknown): number | undefined {
258
- return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
259
- }
260
-
261
- function normalizeSessionFileData(session: SessionFileData): SessionFileData {
262
- return {
263
- ...session,
264
- client_type: session.client_type ?? session.agent_type,
265
- capabilities: Array.isArray(session.capabilities)
266
- ? session.capabilities.filter((entry): entry is string => typeof entry === 'string')
267
- : [...DEFAULT_AGENT_CAPABILITIES],
268
- agent_version: session.agent_version ?? DEFAULT_AGENT_VERSION,
269
- host_id: session.host_id ?? DEFAULT_HOST_ID,
270
- };
271
- }
272
-
273
- function normalizeHeartbeatResult(
274
- raw: AgentHeartbeatResult | Record<string, unknown>,
275
- ): AgentHeartbeatResult {
276
- return {
277
- status: 'ok',
278
- server_time:
279
- typeof raw.server_time === 'string' && raw.server_time.length > 0
280
- ? raw.server_time
281
- : new Date().toISOString(),
282
- ...(asFiniteNumber(raw.next_heartbeat_ms) !== undefined
283
- ? { next_heartbeat_ms: asFiniteNumber(raw.next_heartbeat_ms) }
284
- : {}),
285
- ...(asFiniteNumber(raw.budget_remaining_usd) !== undefined
286
- ? { budget_remaining_usd: asFiniteNumber(raw.budget_remaining_usd) }
287
- : {}),
288
- ...(asFiniteNumber(raw.coalesced_signals) !== undefined
289
- ? { coalesced_signals: asFiniteNumber(raw.coalesced_signals) }
290
- : {}),
291
- ...(typeof raw.assignment === 'object' && raw.assignment !== null
292
- ? { assignment: raw.assignment as AgentHeartbeatResult['assignment'] }
293
- : {}),
294
- };
295
- }
296
-
297
- async function postControlPlaneHeartbeat(
298
- endpoint: string,
299
- token: string,
300
- payload: AgentHeartbeatInput,
301
- fetchFn: typeof fetch,
302
- ): Promise<AgentHeartbeatResult> {
303
- const response = await fetchFn(`${endpoint}${CONTROL_PLANE_HEARTBEAT_PATH}`, {
304
- method: 'POST',
305
- headers: {
306
- authorization: `Bearer ${token}`,
307
- 'content-type': 'application/json',
308
- },
309
- body: JSON.stringify(payload),
310
- });
311
-
312
- if (!response.ok) {
313
- throw new Error(`HTTP ${response.status}`);
314
- }
315
-
316
- const raw = (await response.json().catch(() => ({}))) as AgentHeartbeatResult;
317
- return normalizeHeartbeatResult(raw);
318
- }
319
-
320
- /**
321
- * Get the session file path for a given session directory
322
- * @param sessionDir - Session directory path
323
- * @returns Full path to current.json
324
- */
325
- function getSessionFilePath(sessionDir: string): string {
326
- return join(sessionDir, SESSION_FILENAME);
327
- }
328
-
329
- function getCanonicalSessionFilePath(sessionDir: string, wuId: string): string {
330
- return join(sessionDir, `${wuId}.json`);
331
- }
332
-
333
- function ensureSessionDirectory(sessionDir: string): void {
334
- if (!existsSync(sessionDir)) {
335
- mkdirSync(sessionDir, { recursive: true });
336
- }
337
- }
338
-
339
- function readSessionFile(filePath: string): SessionFileData | null {
340
- if (!existsSync(filePath)) {
341
- return null;
342
- }
343
-
344
- return normalizeSessionFileData(
345
- JSON.parse(readFileSync(filePath, { encoding: 'utf-8' })) as SessionFileData,
346
- );
347
- }
348
-
349
- function writeSessionFile(filePath: string, session: SessionFileData): void {
350
- writeFileSync(filePath, JSON.stringify(session, null, 2), { encoding: 'utf-8' });
351
- }
352
-
353
- function getCanonicalSessions(sessionDir: string): SessionFileData[] {
354
- if (!existsSync(sessionDir)) {
355
- return [];
356
- }
357
-
358
- return readdirSync(sessionDir)
359
- .filter((entry) => entry.endsWith('.json') && entry !== SESSION_FILENAME)
360
- .map((entry) => readSessionFile(join(sessionDir, entry)))
361
- .filter((session): session is SessionFileData => session !== null);
362
- }
363
-
364
- function getMostRecentActiveSession(sessionDir: string): SessionFileData | null {
365
- const sessions = getCanonicalSessions(sessionDir);
366
- if (sessions.length === 0) {
367
- return null;
368
- }
369
-
370
- const [latest] = [...sessions].sort((left, right) => {
371
- const leftTime = Date.parse(left.started);
372
- const rightTime = Date.parse(right.started);
373
-
374
- if (Number.isNaN(leftTime) && Number.isNaN(rightTime)) {
375
- return left.wu_id.localeCompare(right.wu_id);
376
- }
377
- if (Number.isNaN(leftTime)) {
378
- return 1;
379
- }
380
- if (Number.isNaN(rightTime)) {
381
- return -1;
382
- }
383
-
384
- return rightTime - leftTime;
385
- });
386
-
387
- return latest ?? null;
388
- }
389
-
390
- function refreshCompatibilityPointer(
391
- sessionDir: string,
392
- preferredSession: SessionFileData | null = null,
393
- ): void {
394
- const compatibilityPath = getSessionFilePath(sessionDir);
395
- const activeSession = preferredSession ?? getMostRecentActiveSession(sessionDir);
396
-
397
- if (!activeSession) {
398
- if (existsSync(compatibilityPath)) {
399
- unlinkSync(compatibilityPath);
400
- }
401
- return;
402
- }
403
-
404
- ensureSessionDirectory(sessionDir);
405
- writeSessionFile(compatibilityPath, activeSession);
406
- }
407
-
408
- function resolveSessionForWU(sessionDir: string, wuId: string): SessionFileData | null {
409
- const canonicalSession = readSessionFile(getCanonicalSessionFilePath(sessionDir, wuId));
410
- if (canonicalSession) {
411
- return canonicalSession;
412
- }
413
-
414
- const compatibilitySession = readSessionFile(getSessionFilePath(sessionDir));
415
- if (compatibilitySession?.wu_id === wuId) {
416
- ensureSessionDirectory(sessionDir);
417
- writeSessionFile(getCanonicalSessionFilePath(sessionDir, wuId), compatibilitySession);
418
- refreshCompatibilityPointer(sessionDir, compatibilitySession);
419
- return compatibilitySession;
420
- }
421
-
422
- return null;
423
- }
424
-
425
- function resolveCurrentCompatibilitySession(sessionDir: string): SessionFileData | null {
426
- const compatibilitySession = readSessionFile(getSessionFilePath(sessionDir));
427
- if (compatibilitySession) {
428
- return compatibilitySession;
429
- }
430
-
431
- const latestSession = getMostRecentActiveSession(sessionDir);
432
- if (latestSession) {
433
- refreshCompatibilityPointer(sessionDir, latestSession);
434
- }
435
- return latestSession;
436
- }
437
-
438
- /**
439
- * Options for starting a session for a WU
440
- *
441
- * WU-2754: `lifecycleRole`, `specialtyProfile`, `delegationId` are optional
442
- * ADR-014 extension 2 axes. When omitted, `startSessionForWU` applies
443
- * kernel-agnostic v2 defaults (executor/general) — the CLI pack layer owns
444
- * the canonical vocabulary (see `delegation-role-resolver.ts`).
445
- */
446
- interface StartSessionOptions {
447
- wuId: string;
448
- tier?: 1 | 2 | 3;
449
- agentType?: string;
450
- lane?: string;
451
- sessionDir?: string;
452
- workspaceRoot?: string;
453
- environment?: NodeJS.ProcessEnv;
454
- controlPlaneSyncPort?: ControlPlaneSessionSyncPort;
455
- fetchFn?: typeof fetch;
456
- baseDir?: string;
457
- /** ADR-014 lifecycle_role for this session (e.g. 'orchestrator'). */
458
- lifecycleRole?: string;
459
- /** ADR-014 specialty_profile for this session (e.g. 'delivery'). */
460
- specialtyProfile?: string;
461
- displayName?: string;
462
- /** Explicit stable A2A reader identity. Format hint: <owner>:<role>:<purpose>. */
463
- agentIdentity?: string;
464
- /** Reserved cloud workspace/tenant scope slot; unused by local runtime. */
465
- tenantId?: string;
466
- /** Parent delegation-registry record reference (dlg-XXXX). */
467
- delegationId?: string;
468
- }
469
-
470
- /**
471
- * Result of starting a session
472
- */
473
- interface StartSessionResult {
474
- sessionId: string;
475
- alreadyActive?: boolean;
476
- memoryNodeId?: string | null;
477
- }
478
-
479
- interface HeartbeatSessionOptions {
480
- wuId?: string;
481
- sessionDir?: string;
482
- workspaceRoot?: string;
483
- environment?: NodeJS.ProcessEnv;
484
- controlPlaneSyncPort?: ControlPlaneSessionSyncPort;
485
- heartbeatManager?: HeartbeatManager;
486
- fetchFn?: typeof fetch;
487
- health?: HeartbeatHealth;
488
- }
489
-
490
- interface HeartbeatSessionResult {
491
- sent: boolean;
492
- reason?: 'no_active_session' | 'control_plane_unavailable';
493
- heartbeat?: AgentHeartbeatResult;
494
- }
495
-
496
- /**
497
- * Start a session for a WU (called by wu:claim)
498
- *
499
- * Unlike startSession in agent-session.ts, this function:
500
- * - Does NOT throw if a session already exists (returns existing session)
501
- * - Uses default tier 2 if not specified
502
- * - Supports custom session directory for testing
503
- * - Creates memory layer session node for context restoration (WU-1466)
504
- *
505
- * @param options - Session options
506
- * @returns Session result
507
- */
508
- export async function startSessionForWU(options: StartSessionOptions): Promise<StartSessionResult> {
509
- const {
510
- wuId,
511
- tier = DEFAULT_TIER,
512
- agentType = DEFAULT_AGENT_TYPE,
513
- lane,
514
- sessionDir,
515
- workspaceRoot = process.cwd(),
516
- environment = process.env,
517
- controlPlaneSyncPort,
518
- fetchFn = fetch,
519
- lifecycleRole,
520
- specialtyProfile,
521
- displayName,
522
- agentIdentity,
523
- tenantId,
524
- delegationId,
525
- } = options;
526
-
527
- const sessDir = sessionDir ?? SESSION_DIR;
528
- const canonicalSessionFile = getCanonicalSessionFilePath(sessDir, wuId);
529
-
530
- const existing = resolveSessionForWU(sessDir, wuId);
531
- if (existing) {
532
- refreshCompatibilityPointer(sessDir, existing);
533
- return {
534
- sessionId: existing.session_id,
535
- alreadyActive: true,
536
- };
537
- }
538
-
539
- // Create session directory if needed
540
- ensureSessionDirectory(sessDir);
541
-
542
- const standardizedMetadata = resolveStandardSessionMetadata({
543
- agentType,
544
- environment,
545
- });
546
-
547
- // Create new session
548
- const sessionId = randomUUID();
549
- // WU-2754 (ADR-014 extension 2): every new session is v2-stamped and
550
- // carries lifecycle_role + specialty_profile (caller-supplied or defaulted).
551
- const roleAxes = withV2RoleDefaults({
552
- lifecycle_role: lifecycleRole,
553
- specialty_profile: specialtyProfile,
554
- display_name: displayName,
555
- agent_identity: agentIdentity,
556
- tenant_id: tenantId,
557
- delegation_id: delegationId,
558
- });
559
- const resolvedDisplayName = roleAxes.display_name ?? acquireDisplayName(workspaceRoot, sessionId);
560
- const session: SessionFileData = {
561
- session_id: sessionId,
562
- wu_id: wuId,
563
- started: new Date().toISOString(),
564
- last_heartbeat: new Date().toISOString(),
565
- lane,
566
- agent_type: agentType,
567
- client_type: standardizedMetadata.client_type,
568
- capabilities: standardizedMetadata.capabilities,
569
- agent_version: standardizedMetadata.agent_version,
570
- host_id: standardizedMetadata.host_id,
571
- context_tier: tier,
572
- incidents_logged: 0,
573
- incidents_major: 0,
574
- auto_started: true, // Mark as auto-started by wu:claim
575
- schemaVersion: SESSION_SCHEMA_VERSION_V2,
576
- lifecycle_role: roleAxes.lifecycle_role,
577
- specialty_profile: roleAxes.specialty_profile,
578
- display_name: resolvedDisplayName,
579
- ...(roleAxes.agent_identity ? { agent_identity: roleAxes.agent_identity } : {}),
580
- ...(roleAxes.tenant_id ? { tenant_id: roleAxes.tenant_id } : {}),
581
- ...(roleAxes.delegation_id ? { delegation_id: roleAxes.delegation_id } : {}),
582
- };
583
-
584
- writeSessionFile(canonicalSessionFile, session);
585
- refreshCompatibilityPointer(sessDir, session);
586
-
587
- // WU-2153: Optional control-plane session registration.
588
- // Fail-open by design: session lifecycle must not be blocked by remote errors.
589
- const controlPlaneConfig = resolveControlPlaneSessionSyncConfig(workspaceRoot, environment);
590
- if (controlPlaneConfig) {
591
- const registerInput: RegisterSessionInput = {
592
- workspace_id: controlPlaneConfig.workspaceId,
593
- session_id: sessionId,
594
- agent_id: agentType,
595
- started_at: session.started,
596
- lane,
597
- wu_id: wuId,
598
- client_type: standardizedMetadata.client_type,
599
- capabilities: standardizedMetadata.capabilities,
600
- agent_version: standardizedMetadata.agent_version,
601
- host_id: standardizedMetadata.host_id,
602
- metadata: standardizedMetadata.metadata,
603
- };
604
-
605
- try {
606
- if (controlPlaneSyncPort) {
607
- await controlPlaneSyncPort.registerSession(registerInput);
608
- } else {
609
- await postControlPlaneSessionHook(
610
- controlPlaneConfig.endpoint,
611
- controlPlaneConfig.token,
612
- CONTROL_PLANE_REGISTER_PATH,
613
- registerInput,
614
- fetchFn,
615
- );
616
- }
617
- } catch {
618
- // Fail-open: remote registration must not block wu:claim.
619
- }
620
- }
621
-
622
- return {
623
- sessionId,
624
- alreadyActive: false,
625
- };
626
- }
627
-
628
- /**
629
- * Send a control-plane heartbeat for the active WU session.
630
- *
631
- * This is fail-open for local workflows: if no active session or no control-plane config
632
- * is present, no heartbeat is sent and a structured reason is returned.
633
- */
634
- export async function heartbeatSessionForWU(
635
- options: HeartbeatSessionOptions = {},
636
- ): Promise<HeartbeatSessionResult> {
637
- const {
638
- wuId,
639
- sessionDir,
640
- workspaceRoot = process.cwd(),
641
- environment = process.env,
642
- controlPlaneSyncPort,
643
- heartbeatManager,
644
- fetchFn = fetch,
645
- health,
646
- } = options;
647
- const sessDir = sessionDir ?? SESSION_DIR;
648
-
649
- const currentSession = getCurrentSessionForWU({ sessionDir, wuId });
650
- if (!currentSession) {
651
- return {
652
- sent: false,
653
- reason: 'no_active_session',
654
- };
655
- }
656
-
657
- const controlPlaneConfig = resolveControlPlaneSessionSyncConfig(workspaceRoot, environment);
658
- if (!controlPlaneConfig) {
659
- return {
660
- sent: false,
661
- reason: 'control_plane_unavailable',
662
- };
663
- }
664
-
665
- const heartbeatPort = controlPlaneSyncPort?.heartbeat
666
- ? {
667
- heartbeat: controlPlaneSyncPort.heartbeat.bind(controlPlaneSyncPort),
668
- }
669
- : {
670
- heartbeat: async (input: AgentHeartbeatInput): Promise<AgentHeartbeatResult> =>
671
- postControlPlaneHeartbeat(
672
- controlPlaneConfig.endpoint,
673
- controlPlaneConfig.token,
674
- input,
675
- fetchFn,
676
- ),
677
- };
678
-
679
- const manager = heartbeatManager ?? new HeartbeatManager(heartbeatPort);
680
- const heartbeat = await manager.heartbeat({
681
- workspace_id: controlPlaneConfig.workspaceId,
682
- session_id: currentSession.session_id,
683
- agent_id: currentSession.agent_type,
684
- wu_id: currentSession.wu_id,
685
- ...(health ? { health } : {}),
686
- });
687
-
688
- currentSession.last_heartbeat = new Date().toISOString();
689
- writeSessionFile(getCanonicalSessionFilePath(sessDir, currentSession.wu_id), currentSession);
690
- refreshCompatibilityPointer(sessDir, currentSession);
691
-
692
- return {
693
- sent: true,
694
- heartbeat,
695
- };
696
- }
697
-
698
- /**
699
- * Options for ending a session
700
- */
701
- interface EndSessionOptions {
702
- wuId?: string;
703
- sessionDir?: string;
704
- workspaceRoot?: string;
705
- environment?: NodeJS.ProcessEnv;
706
- controlPlaneSyncPort?: ControlPlaneSessionSyncPort;
707
- fetchFn?: typeof fetch;
708
- }
709
-
710
- /**
711
- * Session summary
712
- *
713
- * WU-2754: includes optional ADR-014 extension 2 axes (lifecycle_role,
714
- * specialty_profile, delegation_id). Absent on v1 legacy sessions.
715
- */
716
- interface SessionSummary {
717
- wu_id: string;
718
- session_id: string;
719
- started: string;
720
- completed: string;
721
- agent_type: string;
722
- context_tier: number;
723
- incidents_logged: number;
724
- incidents_major: number;
725
- /** ADR-014 extension 2 — present on v2 sessions (WU-2754). */
726
- lifecycle_role?: string;
727
- /** ADR-014 extension 2 — present on v2 sessions (WU-2754). */
728
- specialty_profile?: string;
729
- display_name?: string;
730
- /** ADR-014 extension 2 — present when session linked to a delegation (WU-2754). */
731
- delegation_id?: string;
732
- /** ADR-014 extension 2 — 'v2' when session was written with the v2 schema (WU-2754). */
733
- schemaVersion?: SessionSchemaVersion;
734
- }
735
-
736
- /**
737
- * Result of ending a session
738
- */
739
- interface EndSessionResult {
740
- ended: boolean;
741
- summary?: SessionSummary;
742
- reason?: string;
743
- }
744
-
745
- /**
746
- * End the current session (called by wu:done)
747
- *
748
- * Unlike endSession in agent-session.ts, this function:
749
- * - Does NOT throw if no active session (returns { ended: false })
750
- * - Returns structured result with summary
751
- * - Supports custom session directory for testing
752
- *
753
- * @param options - Session options
754
- * @returns Session end result
755
- */
756
- export function endSessionForWU(options: EndSessionOptions = {}): EndSessionResult {
757
- const {
758
- wuId,
759
- sessionDir,
760
- workspaceRoot = process.cwd(),
761
- environment = process.env,
762
- controlPlaneSyncPort,
763
- fetchFn = fetch,
764
- } = options;
765
-
766
- const sessDir = sessionDir ?? SESSION_DIR;
767
- const canonicalSessionFile =
768
- typeof wuId === 'string' && wuId.length > 0 ? getCanonicalSessionFilePath(sessDir, wuId) : null;
769
- const compatibilitySessionFile = getSessionFilePath(sessDir);
770
- const session =
771
- (typeof wuId === 'string' && wuId.length > 0
772
- ? resolveSessionForWU(sessDir, wuId)
773
- : resolveCurrentCompatibilitySession(sessDir)) ?? null;
774
-
775
- if (!session) {
776
- return {
777
- ended: false,
778
- reason: 'no_active_session',
779
- };
780
- }
781
-
782
- // Finalize session
783
- session.completed = new Date().toISOString();
784
- releaseDisplayName(workspaceRoot, session.session_id);
785
-
786
- // Build summary for WU YAML
787
- // WU-2754 (ADR-014 extension 2): include the three role axes when the
788
- // session was stamped v2 or otherwise carries them.
789
- const summary: SessionSummary = {
790
- wu_id: session.wu_id,
791
- session_id: session.session_id,
792
- started: session.started,
793
- completed: session.completed,
794
- agent_type: session.agent_type,
795
- context_tier: session.context_tier,
796
- incidents_logged: session.incidents_logged,
797
- incidents_major: session.incidents_major,
798
- ...(session.schemaVersion ? { schemaVersion: session.schemaVersion } : {}),
799
- ...(session.lifecycle_role ? { lifecycle_role: session.lifecycle_role } : {}),
800
- ...(session.specialty_profile ? { specialty_profile: session.specialty_profile } : {}),
801
- ...(session.display_name ? { display_name: session.display_name } : {}),
802
- ...(session.delegation_id ? { delegation_id: session.delegation_id } : {}),
803
- };
804
-
805
- const resolvedCanonicalSessionFile = getCanonicalSessionFilePath(sessDir, session.wu_id);
806
- if (existsSync(resolvedCanonicalSessionFile)) {
807
- unlinkSync(resolvedCanonicalSessionFile);
808
- }
809
-
810
- const compatibilitySession = readSessionFile(compatibilitySessionFile);
811
- if (
812
- compatibilitySession?.session_id === session.session_id &&
813
- existsSync(compatibilitySessionFile)
814
- ) {
815
- unlinkSync(compatibilitySessionFile);
816
- }
817
-
818
- if (canonicalSessionFile && existsSync(canonicalSessionFile)) {
819
- unlinkSync(canonicalSessionFile);
820
- }
821
- refreshCompatibilityPointer(sessDir);
822
-
823
- // WU-2153: Optional control-plane session deregistration.
824
- // Fail-open by design: completion path must not block on remote errors.
825
- const controlPlaneConfig = resolveControlPlaneSessionSyncConfig(workspaceRoot, environment);
826
- if (controlPlaneConfig) {
827
- const deregisterInput: DeregisterSessionInput = {
828
- workspace_id: controlPlaneConfig.workspaceId,
829
- session_id: session.session_id,
830
- ended_at: session.completed,
831
- reason: 'wu_done',
832
- };
833
-
834
- if (controlPlaneSyncPort) {
835
- void controlPlaneSyncPort.deregisterSession(deregisterInput).catch(() => {});
836
- } else {
837
- void postControlPlaneSessionHook(
838
- controlPlaneConfig.endpoint,
839
- controlPlaneConfig.token,
840
- CONTROL_PLANE_DEREGISTER_PATH,
841
- deregisterInput,
842
- fetchFn,
843
- ).catch(() => {});
844
- }
845
- }
846
-
847
- return {
848
- ended: true,
849
- summary,
850
- };
851
- }
852
-
853
- /**
854
- * Options for getting current session
855
- */
856
- interface GetSessionOptions {
857
- wuId?: string;
858
- sessionDir?: string;
859
- }
860
-
861
- /**
862
- * Get the current active session
863
- *
864
- * @param options - Session options
865
- * @returns Session object or null if no active session
866
- */
867
- export function getCurrentSessionForWU(options: GetSessionOptions = {}): SessionFileData | null {
868
- const { wuId, sessionDir } = options;
869
-
870
- const sessDir = sessionDir ?? SESSION_DIR;
871
- if (typeof wuId === 'string' && wuId.length > 0) {
872
- return resolveSessionForWU(sessDir, wuId);
873
- }
874
-
875
- return resolveCurrentCompatibilitySession(sessDir);
876
- }
877
-
878
- /**
879
- * Check if there's an active session for a specific WU
880
- *
881
- * @param wuId - WU ID to check
882
- * @param options - Session options
883
- * @returns True if session exists and matches WU ID
884
- */
885
- export function hasActiveSessionForWU(wuId: string, options: GetSessionOptions = {}): boolean {
886
- const session = getCurrentSessionForWU(options);
887
- return session !== null && session.wu_id === wuId;
888
- }