@lumenflow/cli 5.4.0 → 5.7.12

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 (227) hide show
  1. package/README.md +42 -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/gate-defaults.js +191 -9
  7. package/dist/gate-defaults.js.map +1 -1
  8. package/dist/gate-registry.js.map +1 -1
  9. package/dist/gates/monolithic-file-contention-guard.js +167 -0
  10. package/dist/gates/monolithic-file-contention-guard.js.map +1 -0
  11. package/dist/gates/prod-migration-drift.js +207 -0
  12. package/dist/gates/prod-migration-drift.js.map +1 -0
  13. package/dist/gates/test-over-deletion-guard.js +255 -0
  14. package/dist/gates/test-over-deletion-guard.js.map +1 -0
  15. package/dist/gates-runners.js +401 -2
  16. package/dist/gates-runners.js.map +1 -1
  17. package/dist/gates.js +349 -4
  18. package/dist/gates.js.map +1 -1
  19. package/dist/lumenflow-setup.js +144 -0
  20. package/dist/lumenflow-setup.js.map +1 -0
  21. package/dist/lumenflow-upgrade.js +2 -1
  22. package/dist/lumenflow-upgrade.js.map +1 -1
  23. package/dist/mem-create.js +10 -1
  24. package/dist/mem-create.js.map +1 -1
  25. package/dist/mem-signal.js +21 -4
  26. package/dist/mem-signal.js.map +1 -1
  27. package/dist/metrics-cli.js +19 -2
  28. package/dist/metrics-cli.js.map +1 -1
  29. package/dist/metrics-snapshot.js +25 -2
  30. package/dist/metrics-snapshot.js.map +1 -1
  31. package/dist/orchestrate-initiative.js +28 -3
  32. package/dist/orchestrate-initiative.js.map +1 -1
  33. package/dist/public-manifest.js +17 -0
  34. package/dist/public-manifest.js.map +1 -1
  35. package/dist/release.js +53 -18
  36. package/dist/release.js.map +1 -1
  37. package/dist/wu-done-gates.js +121 -8
  38. package/dist/wu-done-gates.js.map +1 -1
  39. package/dist/wu-done.js +30 -6
  40. package/dist/wu-done.js.map +1 -1
  41. package/dist/wu-edit-operations.js +74 -0
  42. package/dist/wu-edit-operations.js.map +1 -1
  43. package/dist/wu-edit-validators.js +58 -0
  44. package/dist/wu-edit-validators.js.map +1 -1
  45. package/dist/wu-edit.js +106 -4
  46. package/dist/wu-edit.js.map +1 -1
  47. package/dist/wu-prep.js +132 -8
  48. package/dist/wu-prep.js.map +1 -1
  49. package/dist/wu-recover.js +6 -0
  50. package/dist/wu-recover.js.map +1 -1
  51. package/dist/wu-release.js +120 -2
  52. package/dist/wu-release.js.map +1 -1
  53. package/dist/wu-sizing-validation.js +47 -17
  54. package/dist/wu-sizing-validation.js.map +1 -1
  55. package/dist/wu-status.js +33 -0
  56. package/dist/wu-status.js.map +1 -1
  57. package/package.json +13 -11
  58. package/packs/agent-runtime/package.json +1 -1
  59. package/packs/sidekick/package.json +1 -1
  60. package/packs/software-delivery/package.json +1 -1
  61. package/templates/core/AGENTS.md.template +162 -26
  62. package/templates/core/LUMENFLOW.md.template +381 -70
  63. package/templates/core/ai/onboarding/agent-invocation-guide.md.template +0 -5
  64. package/templates/core/ai/onboarding/agent-safety-card.md.template +63 -17
  65. package/templates/core/ai/onboarding/initiative-orchestration.md.template +4 -0
  66. package/templates/core/ai/onboarding/release-process.md.template +7 -7
  67. package/templates/core/ai/onboarding/vendor-support.md.template +74 -10
  68. package/templates/vendors/claude/.claude/skills/frontend-design/SKILL.md.template +1 -1
  69. package/templates/vendors/claude/.claude/skills/wu-lifecycle/SKILL.md.template +28 -0
  70. package/packs/agent-runtime/agent-heartbeat.ts +0 -163
  71. package/packs/agent-runtime/auto-session-integration.ts +0 -888
  72. package/packs/agent-runtime/capability-factory.ts +0 -104
  73. package/packs/agent-runtime/constants.ts +0 -21
  74. package/packs/agent-runtime/delegation-registry-schema.ts +0 -220
  75. package/packs/agent-runtime/delegation-registry-store.ts +0 -269
  76. package/packs/agent-runtime/delegation-tree.ts +0 -328
  77. package/packs/agent-runtime/index.ts +0 -20
  78. package/packs/agent-runtime/manifest.ts +0 -348
  79. package/packs/agent-runtime/memory-coordination-contract.ts +0 -86
  80. package/packs/agent-runtime/orchestration.ts +0 -2027
  81. package/packs/agent-runtime/pack-registration.ts +0 -110
  82. package/packs/agent-runtime/policy-factory.ts +0 -165
  83. package/packs/agent-runtime/remote-controls/index.ts +0 -7
  84. package/packs/agent-runtime/remote-controls/operations.ts +0 -405
  85. package/packs/agent-runtime/remote-controls/port.ts +0 -48
  86. package/packs/agent-runtime/remote-controls/state-store.ts +0 -258
  87. package/packs/agent-runtime/remote-controls/types.ts +0 -105
  88. package/packs/agent-runtime/session-schema.ts +0 -467
  89. package/packs/agent-runtime/tool-impl/agent-turn-tools.ts +0 -793
  90. package/packs/agent-runtime/tool-impl/index.ts +0 -6
  91. package/packs/agent-runtime/tool-impl/provider-adapters.ts +0 -1245
  92. package/packs/agent-runtime/tool-impl/remote-controls.mock.ts +0 -256
  93. package/packs/agent-runtime/tool-impl/remote-controls.ts +0 -273
  94. package/packs/agent-runtime/tools/index.ts +0 -4
  95. package/packs/agent-runtime/tools/types.ts +0 -47
  96. package/packs/agent-runtime/turn-lifecycle-events.ts +0 -590
  97. package/packs/agent-runtime/types.ts +0 -128
  98. package/packs/agent-runtime/vitest.config.ts +0 -11
  99. package/packs/sidekick/channel-ingress.ts +0 -137
  100. package/packs/sidekick/constants.ts +0 -10
  101. package/packs/sidekick/index.ts +0 -8
  102. package/packs/sidekick/manifest-schema.ts +0 -49
  103. package/packs/sidekick/manifest.ts +0 -512
  104. package/packs/sidekick/pack-registration.ts +0 -110
  105. package/packs/sidekick/policy-factory.ts +0 -38
  106. package/packs/sidekick/sidekick-events.ts +0 -694
  107. package/packs/sidekick/src/adapters/cloud-queue.ts +0 -101
  108. package/packs/sidekick/src/adapters/control-plane-bridge.adapter.ts +0 -386
  109. package/packs/sidekick/src/adapters/filesystem-bridge.adapter.ts +0 -228
  110. package/packs/sidekick/src/domain/channel.types.ts +0 -64
  111. package/packs/sidekick/src/ports/channel-bridge.port.ts +0 -92
  112. package/packs/sidekick/src/routines/commit.ts +0 -74
  113. package/packs/sidekick/tool-impl/channel-tools.ts +0 -577
  114. package/packs/sidekick/tool-impl/channel-transports.ts +0 -75
  115. package/packs/sidekick/tool-impl/index.ts +0 -29
  116. package/packs/sidekick/tool-impl/memory-tools.ts +0 -290
  117. package/packs/sidekick/tool-impl/routine-commit.ts +0 -102
  118. package/packs/sidekick/tool-impl/routine-tools.ts +0 -440
  119. package/packs/sidekick/tool-impl/runtime-context.ts +0 -28
  120. package/packs/sidekick/tool-impl/shared.ts +0 -125
  121. package/packs/sidekick/tool-impl/storage.ts +0 -325
  122. package/packs/sidekick/tool-impl/system-tools.ts +0 -160
  123. package/packs/sidekick/tool-impl/task-tools.ts +0 -506
  124. package/packs/sidekick/tools/channel-tools.ts +0 -53
  125. package/packs/sidekick/tools/index.ts +0 -9
  126. package/packs/sidekick/tools/memory-tools.ts +0 -53
  127. package/packs/sidekick/tools/routine-tools.ts +0 -53
  128. package/packs/sidekick/tools/system-tools.ts +0 -47
  129. package/packs/sidekick/tools/task-tools.ts +0 -61
  130. package/packs/sidekick/tools/types.ts +0 -57
  131. package/packs/sidekick/vitest.config.ts +0 -11
  132. package/packs/software-delivery/constants.ts +0 -10
  133. package/packs/software-delivery/extensions.ts +0 -140
  134. package/packs/software-delivery/gate-policies.ts +0 -134
  135. package/packs/software-delivery/index.ts +0 -8
  136. package/packs/software-delivery/manifest-schema.ts +0 -268
  137. package/packs/software-delivery/manifest.ts +0 -657
  138. package/packs/software-delivery/pack-registration.ts +0 -113
  139. package/packs/software-delivery/src/commands/index.ts +0 -5
  140. package/packs/software-delivery/src/config/delivery-review-contract.ts +0 -256
  141. package/packs/software-delivery/src/config/env-accessors.ts +0 -66
  142. package/packs/software-delivery/src/config/index.ts +0 -8
  143. package/packs/software-delivery/src/config/normalize-config-keys.ts +0 -9
  144. package/packs/software-delivery/src/config/schemas/lumenflow-config-schema-types.ts +0 -460
  145. package/packs/software-delivery/src/config/workspace-reader.ts +0 -375
  146. package/packs/software-delivery/src/constants/backlog-patterns.ts +0 -31
  147. package/packs/software-delivery/src/constants/client-ids.ts +0 -19
  148. package/packs/software-delivery/src/constants/config-contract.ts +0 -7
  149. package/packs/software-delivery/src/constants/docs-layout-presets.ts +0 -50
  150. package/packs/software-delivery/src/constants/duration-constants.ts +0 -20
  151. package/packs/software-delivery/src/constants/gate-constants.ts +0 -32
  152. package/packs/software-delivery/src/constants/index.ts +0 -29
  153. package/packs/software-delivery/src/constants/lock-constants.ts +0 -35
  154. package/packs/software-delivery/src/constants/object-guards.ts +0 -12
  155. package/packs/software-delivery/src/constants/section-headings.ts +0 -107
  156. package/packs/software-delivery/src/constants/wu-cli-constants.ts +0 -488
  157. package/packs/software-delivery/src/constants/wu-domain-constants.ts +0 -466
  158. package/packs/software-delivery/src/constants/wu-git-constants.ts +0 -7
  159. package/packs/software-delivery/src/constants/wu-id-format.ts +0 -327
  160. package/packs/software-delivery/src/constants/wu-paths-constants.ts +0 -384
  161. package/packs/software-delivery/src/constants/wu-statuses.ts +0 -287
  162. package/packs/software-delivery/src/constants/wu-type-helpers.ts +0 -67
  163. package/packs/software-delivery/src/constants/wu-ui-constants.ts +0 -267
  164. package/packs/software-delivery/src/constants/wu-validation-constants.ts +0 -73
  165. package/packs/software-delivery/src/domain/index.ts +0 -5
  166. package/packs/software-delivery/src/domain/orchestration.constants.ts +0 -166
  167. package/packs/software-delivery/src/domain/orchestration.schemas.ts +0 -238
  168. package/packs/software-delivery/src/domain/orchestration.types.ts +0 -176
  169. package/packs/software-delivery/src/methodology/incremental-test.ts +0 -122
  170. package/packs/software-delivery/src/methodology/index.ts +0 -6
  171. package/packs/software-delivery/src/methodology/manual-test-validator.ts +0 -292
  172. package/packs/software-delivery/src/policy/coverage-gate.ts +0 -270
  173. package/packs/software-delivery/src/policy/gates-agent-mode.ts +0 -223
  174. package/packs/software-delivery/src/policy/gates-config-internal.ts +0 -121
  175. package/packs/software-delivery/src/policy/gates-config.ts +0 -300
  176. package/packs/software-delivery/src/policy/gates-coverage.ts +0 -356
  177. package/packs/software-delivery/src/policy/gates-presets.ts +0 -134
  178. package/packs/software-delivery/src/policy/gates-schemas.ts +0 -173
  179. package/packs/software-delivery/src/policy/index.ts +0 -22
  180. package/packs/software-delivery/src/policy/package-manager-resolver.ts +0 -319
  181. package/packs/software-delivery/src/policy/resolve-policy.ts +0 -601
  182. package/packs/software-delivery/src/ports/config.ports.ts +0 -90
  183. package/packs/software-delivery/src/ports/dashboard-renderer.port.ts +0 -125
  184. package/packs/software-delivery/src/ports/index.ts +0 -10
  185. package/packs/software-delivery/src/ports/sync-validator.ports.ts +0 -59
  186. package/packs/software-delivery/src/ports/wu-helpers.ports.ts +0 -168
  187. package/packs/software-delivery/src/ports/wu-state.ports.ts +0 -241
  188. package/packs/software-delivery/src/primitives/index.ts +0 -5
  189. package/packs/software-delivery/src/runtime/index.ts +0 -6
  190. package/packs/software-delivery/src/runtime/work-classifier.ts +0 -561
  191. package/packs/software-delivery/src/sandbox/index.ts +0 -10
  192. package/packs/software-delivery/src/sandbox/sandbox-allowlist.ts +0 -118
  193. package/packs/software-delivery/src/sandbox/sandbox-backend-linux.ts +0 -88
  194. package/packs/software-delivery/src/sandbox/sandbox-backend-macos.ts +0 -154
  195. package/packs/software-delivery/src/sandbox/sandbox-backend-windows.ts +0 -47
  196. package/packs/software-delivery/src/sandbox/sandbox-profile.ts +0 -153
  197. package/packs/software-delivery/src/schemas/index.ts +0 -5
  198. package/packs/software-delivery/src/state/date-utils.ts +0 -158
  199. package/packs/software-delivery/src/state/index.ts +0 -15
  200. package/packs/software-delivery/src/state/state-machine.ts +0 -119
  201. package/packs/software-delivery/src/state/wu-doc-types.ts +0 -51
  202. package/packs/software-delivery/src/state/wu-paths.ts +0 -381
  203. package/packs/software-delivery/src/state/wu-schema.ts +0 -1139
  204. package/packs/software-delivery/src/state/wu-state-schema.ts +0 -255
  205. package/packs/software-delivery/src/state/wu-yaml.ts +0 -338
  206. package/packs/software-delivery/tool-impl/agent-tools.ts +0 -263
  207. package/packs/software-delivery/tool-impl/delegation-tools.ts +0 -66
  208. package/packs/software-delivery/tool-impl/flow-metrics-tools.ts +0 -219
  209. package/packs/software-delivery/tool-impl/git-runner.ts +0 -113
  210. package/packs/software-delivery/tool-impl/git-tools.ts +0 -316
  211. package/packs/software-delivery/tool-impl/index.ts +0 -15
  212. package/packs/software-delivery/tool-impl/initiative-orchestration-tools.ts +0 -720
  213. package/packs/software-delivery/tool-impl/lane-lock.ts +0 -246
  214. package/packs/software-delivery/tool-impl/memory-tools.ts +0 -470
  215. package/packs/software-delivery/tool-impl/pending-runtime-tools.ts +0 -21
  216. package/packs/software-delivery/tool-impl/runtime-cli-adapter.ts +0 -329
  217. package/packs/software-delivery/tool-impl/runtime-native-tools.ts +0 -687
  218. package/packs/software-delivery/tool-impl/worker-loader.ts +0 -52
  219. package/packs/software-delivery/tool-impl/worktree-tools.ts +0 -46
  220. package/packs/software-delivery/tool-impl/wu-lifecycle-tools.ts +0 -807
  221. package/packs/software-delivery/tools/delegation-tools.ts +0 -23
  222. package/packs/software-delivery/tools/git-tools.ts +0 -55
  223. package/packs/software-delivery/tools/index.ts +0 -8
  224. package/packs/software-delivery/tools/lane-lock-tool.ts +0 -37
  225. package/packs/software-delivery/tools/types.ts +0 -71
  226. package/packs/software-delivery/tools/worktree-tools.ts +0 -49
  227. 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
- }