@lumenflow/cli 4.23.0 → 5.0.0
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/README.md +54 -52
- package/dist/agent-issues-query.js +10 -2
- package/dist/agent-issues-query.js.map +1 -1
- package/dist/agent-runtime-enrollment-events.js +44 -0
- package/dist/agent-runtime-enrollment-events.js.map +1 -0
- package/dist/agent-session-end.js +47 -0
- package/dist/agent-session-end.js.map +1 -1
- package/dist/agent-session-heartbeat.js +250 -0
- package/dist/agent-session-heartbeat.js.map +1 -0
- package/dist/agent-session.js +299 -5
- package/dist/agent-session.js.map +1 -1
- package/dist/capacity-snapshot-emitter.js +73 -0
- package/dist/capacity-snapshot-emitter.js.map +1 -0
- package/dist/claim-queue.js +276 -0
- package/dist/claim-queue.js.map +1 -0
- package/dist/config-set.js +22 -3
- package/dist/config-set.js.map +1 -1
- package/dist/control-plane-sidecar-runner.js +145 -0
- package/dist/control-plane-sidecar-runner.js.map +1 -0
- package/dist/delegation-list.js +160 -1
- package/dist/delegation-list.js.map +1 -1
- package/dist/delegation-role-resolver.js +69 -0
- package/dist/delegation-role-resolver.js.map +1 -0
- package/dist/docs-generate-pack-reference.js +500 -0
- package/dist/docs-generate-pack-reference.js.map +1 -0
- package/dist/docs-sync.js +116 -1
- package/dist/docs-sync.js.map +1 -1
- package/dist/file-edit.js +28 -8
- package/dist/file-edit.js.map +1 -1
- package/dist/file-write.js +29 -5
- package/dist/file-write.js.map +1 -1
- package/dist/gate-co-change.js +25 -7
- package/dist/gate-co-change.js.map +1 -1
- package/dist/gate-conditional.js +19 -7
- package/dist/gate-conditional.js.map +1 -1
- package/dist/gates-runners.js +42 -33
- package/dist/gates-runners.js.map +1 -1
- package/dist/gates-utils.js +34 -20
- package/dist/gates-utils.js.map +1 -1
- package/dist/gates.js +79 -7
- package/dist/gates.js.map +1 -1
- package/dist/hooks/config-resolver.js +10 -1
- package/dist/hooks/config-resolver.js.map +1 -1
- package/dist/init-package-config.js +1 -1
- package/dist/init-package-config.js.map +1 -1
- package/dist/init-scaffolding.js +5 -1
- package/dist/init-scaffolding.js.map +1 -1
- package/dist/init-templates.js +10 -0
- package/dist/init-templates.js.map +1 -1
- package/dist/init.js +1 -1
- package/dist/init.js.map +1 -1
- package/dist/initiative-create.js +17 -0
- package/dist/initiative-create.js.map +1 -1
- package/dist/initiative-remove-wu.js +17 -3
- package/dist/initiative-remove-wu.js.map +1 -1
- package/dist/kernel-event-sync/emitters.js +104 -0
- package/dist/kernel-event-sync/emitters.js.map +1 -0
- package/dist/kernel-event-sync/index.js +13 -0
- package/dist/kernel-event-sync/index.js.map +1 -0
- package/dist/kernel-event-sync/lifecycle-emitters.js +160 -0
- package/dist/kernel-event-sync/lifecycle-emitters.js.map +1 -0
- package/dist/kernel-event-sync/narrow-emissions.js +89 -0
- package/dist/kernel-event-sync/narrow-emissions.js.map +1 -0
- package/dist/kernel-event-sync/software-delivery-emitters.js +297 -0
- package/dist/kernel-event-sync/software-delivery-emitters.js.map +1 -0
- package/dist/lane-lock.js +14 -1
- package/dist/lane-lock.js.map +1 -1
- package/dist/lane-suggest.js +21 -0
- package/dist/lane-suggest.js.map +1 -1
- package/dist/lumenflow-upgrade.js +7 -5
- package/dist/lumenflow-upgrade.js.map +1 -1
- package/dist/mem-context.js +145 -0
- package/dist/mem-context.js.map +1 -1
- package/dist/mem-create.js +39 -6
- package/dist/mem-create.js.map +1 -1
- package/dist/mem-inbox.js +16 -0
- package/dist/mem-inbox.js.map +1 -1
- package/dist/mem-roster.js +95 -0
- package/dist/mem-roster.js.map +1 -0
- package/dist/mem-signal.js +97 -2
- package/dist/mem-signal.js.map +1 -1
- package/dist/metrics-cli.js +3 -2
- package/dist/metrics-cli.js.map +1 -1
- package/dist/metrics-snapshot.js +271 -13
- package/dist/metrics-snapshot.js.map +1 -1
- package/dist/orchestrate-init-status.js +117 -2
- package/dist/orchestrate-init-status.js.map +1 -1
- package/dist/orchestrate-initiative.js +83 -10
- package/dist/orchestrate-initiative.js.map +1 -1
- package/dist/orchestrate-monitor-quality.js +289 -0
- package/dist/orchestrate-monitor-quality.js.map +1 -0
- package/dist/orchestrate-monitor.js +85 -0
- package/dist/orchestrate-monitor.js.map +1 -1
- package/dist/pack-validate.js +127 -2
- package/dist/pack-validate.js.map +1 -1
- package/dist/plan-create.js +18 -0
- package/dist/plan-create.js.map +1 -1
- package/dist/plan-link.js +13 -0
- package/dist/plan-link.js.map +1 -1
- package/dist/plan-promote.js +14 -0
- package/dist/plan-promote.js.map +1 -1
- package/dist/pre-commit-check.js +4 -3
- package/dist/pre-commit-check.js.map +1 -1
- package/dist/public-manifest.js +17 -3
- package/dist/public-manifest.js.map +1 -1
- package/dist/release.js +10 -10
- package/dist/release.js.map +1 -1
- package/dist/session-cross-link.js +139 -0
- package/dist/session-cross-link.js.map +1 -0
- package/dist/sidecar-manager.js +208 -0
- package/dist/sidecar-manager.js.map +1 -0
- package/dist/state-path-resolvers.js +18 -0
- package/dist/state-path-resolvers.js.map +1 -1
- package/dist/stream-heartbeat.js +151 -0
- package/dist/stream-heartbeat.js.map +1 -0
- package/dist/sync-templates.js +56 -2
- package/dist/sync-templates.js.map +1 -1
- package/dist/wu-block.js +47 -5
- package/dist/wu-block.js.map +1 -1
- package/dist/wu-claim-branch.js +8 -4
- package/dist/wu-claim-branch.js.map +1 -1
- package/dist/wu-claim-state.js +5 -3
- package/dist/wu-claim-state.js.map +1 -1
- package/dist/wu-claim-worktree.js +5 -3
- package/dist/wu-claim-worktree.js.map +1 -1
- package/dist/wu-claim.js +261 -9
- package/dist/wu-claim.js.map +1 -1
- package/dist/wu-done-auto-cleanup.js +3 -2
- package/dist/wu-done-auto-cleanup.js.map +1 -1
- package/dist/wu-done-git-ops.js +12 -8
- package/dist/wu-done-git-ops.js.map +1 -1
- package/dist/wu-done-preflight.js +3 -3
- package/dist/wu-done-preflight.js.map +1 -1
- package/dist/wu-done.js +46 -10
- package/dist/wu-done.js.map +1 -1
- package/dist/wu-lifecycle-sync/gate-scope-resolver.js +16 -0
- package/dist/wu-lifecycle-sync/gate-scope-resolver.js.map +1 -0
- package/dist/wu-lifecycle-sync/kernel-event-sync-shim.js +10 -0
- package/dist/wu-lifecycle-sync/kernel-event-sync-shim.js.map +1 -0
- package/dist/wu-prep.js +363 -22
- package/dist/wu-prep.js.map +1 -1
- package/dist/wu-prune.js +68 -27
- package/dist/wu-prune.js.map +1 -1
- package/dist/wu-release.js +34 -3
- package/dist/wu-release.js.map +1 -1
- package/dist/wu-review.js +167 -0
- package/dist/wu-review.js.map +1 -0
- package/dist/wu-spawn-prompt-builders.js +296 -40
- package/dist/wu-spawn-prompt-builders.js.map +1 -1
- package/dist/wu-spawn-strategy-resolver.js +126 -14
- package/dist/wu-spawn-strategy-resolver.js.map +1 -1
- package/dist/wu-unblock.js +52 -22
- package/dist/wu-unblock.js.map +1 -1
- package/package.json +13 -8
- package/packs/agent-runtime/.turbo/turbo-build.log +1 -1
- package/packs/agent-runtime/.turbo/turbo-test.log +25 -0
- package/packs/agent-runtime/.turbo/turbo-typecheck.log +4 -0
- package/packs/agent-runtime/agent-heartbeat.ts +163 -0
- package/packs/agent-runtime/auto-session-integration.ts +874 -0
- package/packs/agent-runtime/delegation-registry-schema.ts +220 -0
- package/packs/agent-runtime/delegation-registry-store.ts +269 -0
- package/packs/agent-runtime/delegation-tree.ts +328 -0
- package/packs/agent-runtime/index.ts +9 -0
- package/packs/agent-runtime/manifest.ts +103 -19
- package/packs/agent-runtime/manifest.yaml +132 -0
- package/packs/agent-runtime/memory-coordination-contract.ts +86 -0
- package/packs/agent-runtime/memory.d.ts +19 -0
- package/packs/agent-runtime/orchestration.ts +238 -23
- package/packs/agent-runtime/package.json +11 -2
- package/packs/agent-runtime/remote-controls/index.ts +7 -0
- package/packs/agent-runtime/remote-controls/operations.ts +399 -0
- package/packs/agent-runtime/remote-controls/port.ts +48 -0
- package/packs/agent-runtime/remote-controls/state-store.ts +258 -0
- package/packs/agent-runtime/remote-controls/types.ts +105 -0
- package/packs/agent-runtime/session-schema.ts +423 -0
- package/packs/agent-runtime/tool-impl/index.ts +1 -0
- package/packs/agent-runtime/tool-impl/remote-controls.mock.ts +252 -0
- package/packs/agent-runtime/tool-impl/remote-controls.ts +273 -0
- package/packs/agent-runtime/tsconfig.json +1 -1
- package/packs/agent-runtime/turn-lifecycle-events.ts +501 -0
- package/packs/sidekick/.lumenflow/state/conductor/outbox/sidekick-events.jsonl +213 -0
- package/packs/sidekick/.turbo/turbo-build.log +1 -1
- package/packs/sidekick/.turbo/turbo-test.log +25 -0
- package/packs/sidekick/.turbo/turbo-typecheck.log +4 -0
- package/packs/sidekick/channel-ingress.ts +137 -0
- package/packs/sidekick/manifest.ts +74 -0
- package/packs/sidekick/manifest.yaml +88 -0
- package/packs/sidekick/package.json +3 -1
- package/packs/sidekick/sidekick-events.ts +517 -0
- package/packs/sidekick/src/adapters/cloud-queue.ts +101 -0
- package/packs/sidekick/src/adapters/control-plane-bridge.adapter.ts +378 -0
- package/packs/sidekick/src/adapters/filesystem-bridge.adapter.ts +224 -0
- package/packs/sidekick/src/domain/channel.types.ts +84 -0
- package/packs/sidekick/src/ports/channel-bridge.port.ts +75 -0
- package/packs/sidekick/src/routines/commit.ts +74 -0
- package/packs/sidekick/tool-impl/channel-tools.ts +47 -0
- package/packs/sidekick/tool-impl/memory-tools.ts +17 -0
- package/packs/sidekick/tool-impl/routine-commit.ts +102 -0
- package/packs/sidekick/tool-impl/routine-tools.ts +67 -7
- package/packs/sidekick/tool-impl/runtime-context.ts +4 -0
- package/packs/sidekick/tool-impl/storage.ts +3 -0
- package/packs/sidekick/tool-impl/system-tools.ts +7 -0
- package/packs/sidekick/tool-impl/task-tools.ts +46 -0
- package/packs/sidekick/tsconfig.json +1 -1
- package/packs/software-delivery/.turbo/turbo-build.log +1 -1
- package/packs/software-delivery/.turbo/turbo-test.log +63 -0
- package/packs/software-delivery/.turbo/turbo-typecheck.log +4 -0
- package/packs/software-delivery/manifest-schema.ts +30 -0
- package/packs/software-delivery/manifest.ts +99 -1
- package/packs/software-delivery/manifest.yaml +46 -0
- package/packs/software-delivery/package.json +88 -3
- package/packs/software-delivery/src/commands/index.ts +5 -0
- package/packs/software-delivery/src/config/delivery-review-contract.ts +20 -0
- package/packs/software-delivery/src/config/env-accessors.ts +19 -0
- package/packs/software-delivery/src/config/index.ts +8 -0
- package/packs/software-delivery/src/config/normalize-config-keys.ts +19 -0
- package/packs/software-delivery/src/config/schemas/lumenflow-config-schema-types.ts +436 -0
- package/packs/software-delivery/src/config/workspace-reader.ts +310 -0
- package/packs/software-delivery/src/constants/backlog-patterns.ts +31 -0
- package/packs/software-delivery/src/constants/client-ids.ts +19 -0
- package/packs/software-delivery/src/constants/config-contract.ts +7 -0
- package/packs/software-delivery/src/constants/docs-layout-presets.ts +50 -0
- package/packs/software-delivery/src/constants/duration-constants.ts +20 -0
- package/packs/software-delivery/src/constants/gate-constants.ts +32 -0
- package/packs/software-delivery/src/constants/index.ts +29 -0
- package/packs/software-delivery/src/constants/lock-constants.ts +35 -0
- package/packs/software-delivery/src/constants/object-guards.ts +12 -0
- package/packs/software-delivery/src/constants/section-headings.ts +107 -0
- package/packs/software-delivery/src/constants/wu-cli-constants.ts +485 -0
- package/packs/software-delivery/src/constants/wu-domain-constants.ts +466 -0
- package/packs/software-delivery/src/constants/wu-git-constants.ts +7 -0
- package/packs/software-delivery/src/constants/wu-id-format.ts +327 -0
- package/packs/software-delivery/src/constants/wu-paths-constants.ts +358 -0
- package/packs/software-delivery/src/constants/wu-statuses.ts +287 -0
- package/packs/software-delivery/src/constants/wu-type-helpers.ts +67 -0
- package/packs/software-delivery/src/constants/wu-ui-constants.ts +267 -0
- package/packs/software-delivery/src/constants/wu-validation-constants.ts +73 -0
- package/packs/software-delivery/src/domain/index.ts +5 -0
- package/packs/software-delivery/src/domain/orchestration.constants.ts +168 -0
- package/packs/software-delivery/src/domain/orchestration.schemas.ts +239 -0
- package/packs/software-delivery/src/domain/orchestration.types.ts +178 -0
- package/packs/software-delivery/src/methodology/incremental-test.ts +90 -0
- package/packs/software-delivery/src/methodology/index.ts +6 -0
- package/packs/software-delivery/src/methodology/manual-test-validator.ts +292 -0
- package/packs/software-delivery/src/policy/coverage-gate.ts +270 -0
- package/packs/software-delivery/src/policy/gates-agent-mode.ts +223 -0
- package/packs/software-delivery/src/policy/gates-config-internal.ts +121 -0
- package/packs/software-delivery/src/policy/gates-config.ts +293 -0
- package/packs/software-delivery/src/policy/gates-coverage.ts +247 -0
- package/packs/software-delivery/src/policy/gates-presets.ts +134 -0
- package/packs/software-delivery/src/policy/gates-schemas.ts +173 -0
- package/packs/software-delivery/src/policy/index.ts +22 -0
- package/packs/software-delivery/src/policy/package-manager-resolver.ts +319 -0
- package/packs/software-delivery/src/policy/resolve-policy.ts +518 -0
- package/packs/software-delivery/src/ports/config.ports.ts +90 -0
- package/packs/software-delivery/src/ports/dashboard-renderer.port.ts +125 -0
- package/packs/software-delivery/src/ports/index.ts +10 -0
- package/packs/software-delivery/src/ports/sync-validator.ports.ts +59 -0
- package/packs/software-delivery/src/ports/wu-helpers.ports.ts +168 -0
- package/packs/software-delivery/src/ports/wu-state.ports.ts +241 -0
- package/packs/software-delivery/src/primitives/index.ts +5 -0
- package/packs/software-delivery/src/runtime/index.ts +6 -0
- package/packs/software-delivery/src/runtime/work-classifier.ts +561 -0
- package/packs/software-delivery/src/sandbox/index.ts +10 -0
- package/packs/software-delivery/src/sandbox/sandbox-allowlist.ts +118 -0
- package/packs/software-delivery/src/sandbox/sandbox-backend-linux.ts +88 -0
- package/packs/software-delivery/src/sandbox/sandbox-backend-macos.ts +154 -0
- package/packs/software-delivery/src/sandbox/sandbox-backend-windows.ts +47 -0
- package/packs/software-delivery/src/sandbox/sandbox-profile.ts +153 -0
- package/packs/software-delivery/src/schemas/index.ts +5 -0
- package/packs/software-delivery/src/state/date-utils.ts +158 -0
- package/packs/software-delivery/src/state/index.ts +15 -0
- package/packs/software-delivery/src/state/state-machine.ts +119 -0
- package/packs/software-delivery/src/state/wu-doc-types.ts +51 -0
- package/packs/software-delivery/src/state/wu-paths.ts +381 -0
- package/packs/software-delivery/src/state/wu-schema.ts +1139 -0
- package/packs/software-delivery/src/state/wu-state-schema.ts +255 -0
- package/packs/software-delivery/src/state/wu-yaml.ts +338 -0
- package/packs/software-delivery/src/types.d.ts +16 -0
- package/packs/software-delivery/tool-impl/wu-lifecycle-tools.ts +18 -0
- package/packs/software-delivery/tsconfig.json +28 -2
- package/templates/core/AGENTS.md.template +76 -17
- package/templates/core/LUMENFLOW.md.template +265 -66
- package/templates/core/_frameworks/lumenflow/wu-sizing-guide.md.template +180 -116
- package/templates/core/ai/onboarding/agent-invocation-guide.md.template +26 -8
- package/templates/core/ai/onboarding/existing-project-bootstrap.md.template +171 -0
- package/templates/core/ai/onboarding/first-15-mins.md.template +3 -1
- package/templates/core/ai/onboarding/first-wu-mistakes.md.template +1 -1
- package/templates/core/ai/onboarding/initiative-orchestration.md.template +46 -30
- package/templates/core/ai/onboarding/quick-ref-commands.md.template +36 -33
- package/templates/core/ai/onboarding/release-process.md.template +8 -7
- package/templates/core/ai/onboarding/starting-prompt.md.template +2 -0
- package/templates/core/ai/onboarding/troubleshooting-wu-done.md.template +62 -0
- package/templates/vendors/claude/.claude/CLAUDE.md.template +29 -54
- package/templates/vendors/cursor/.cursor/rules/lumenflow.md.template +24 -52
- package/templates/vendors/windsurf/.windsurf/rules/lumenflow.md.template +24 -52
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Copyright (c) 2026 Hellmai Ltd
|
|
2
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
|
+
//
|
|
4
|
+
// WU-2732 (INIT-060 Phase 3, ADR-013 §1 + §6 tracer bullet):
|
|
5
|
+
// Remote-control tool name registry and port/result shapes. The six
|
|
6
|
+
// tools here form the conductor-mode governance surface for the
|
|
7
|
+
// agent-runtime pack. Real implementations land in WU-2733; this file
|
|
8
|
+
// defines the wire contract the real impl must satisfy without
|
|
9
|
+
// re-deciding tool names or payload shapes.
|
|
10
|
+
|
|
11
|
+
import type { AgentRuntimeCleanupStatus } from '../turn-lifecycle-events.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Canonical remote-control tool names. Prefixed with the pack slug per
|
|
15
|
+
* ADR-013 §Event Namespacing convention (extended to tool names for
|
|
16
|
+
* consistency with WU-2729 software-delivery pack exposure precedent).
|
|
17
|
+
*/
|
|
18
|
+
export const AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES = {
|
|
19
|
+
RESUME_WORKFLOW: 'agent-runtime:resume_workflow',
|
|
20
|
+
PAUSE_TURN: 'agent-runtime:pause_turn',
|
|
21
|
+
ABORT_TURN: 'agent-runtime:abort_turn',
|
|
22
|
+
ELEVATE_AUTONOMY: 'agent-runtime:elevate_autonomy',
|
|
23
|
+
LOWER_AUTONOMY: 'agent-runtime:lower_autonomy',
|
|
24
|
+
APPROVE_INFLIGHT: 'agent-runtime:approve_inflight',
|
|
25
|
+
} as const;
|
|
26
|
+
|
|
27
|
+
export type AgentRuntimeRemoteControlToolName =
|
|
28
|
+
(typeof AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES)[keyof typeof AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Declaration order matters: callers (HTTP surface allowlist, conductor
|
|
32
|
+
* UI) render the list in this sequence without re-sorting.
|
|
33
|
+
*/
|
|
34
|
+
export const AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAME_VALUES = [
|
|
35
|
+
AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.RESUME_WORKFLOW,
|
|
36
|
+
AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.PAUSE_TURN,
|
|
37
|
+
AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.ABORT_TURN,
|
|
38
|
+
AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.ELEVATE_AUTONOMY,
|
|
39
|
+
AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.LOWER_AUTONOMY,
|
|
40
|
+
AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.APPROVE_INFLIGHT,
|
|
41
|
+
] as const satisfies readonly AgentRuntimeRemoteControlToolName[];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Well-known approval identifier the agent-runtime pack requests for
|
|
45
|
+
* safety-critical remote controls (abort, pause, elevate). Single
|
|
46
|
+
* identifier per ADR-013 §6 governance lock — cloud renders the same
|
|
47
|
+
* approval gate regardless of which of the gated tools was invoked.
|
|
48
|
+
*/
|
|
49
|
+
export const AGENT_RUNTIME_REMOTE_CONTROL_APPROVAL_ID = 'agent-runtime:remote_control' as const;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Mapping from remote-control tool name to its declared
|
|
53
|
+
* `required_approvals` manifest entry. Gated tools request an explicit
|
|
54
|
+
* approval identifier; safer/read-only tools declare an empty array so
|
|
55
|
+
* the manifest stays minimally descriptive (matches WU-2729 convention).
|
|
56
|
+
*/
|
|
57
|
+
export const AGENT_RUNTIME_REMOTE_CONTROL_APPROVALS: Record<
|
|
58
|
+
AgentRuntimeRemoteControlToolName,
|
|
59
|
+
readonly string[]
|
|
60
|
+
> = {
|
|
61
|
+
[AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.RESUME_WORKFLOW]: [],
|
|
62
|
+
[AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.PAUSE_TURN]: [AGENT_RUNTIME_REMOTE_CONTROL_APPROVAL_ID],
|
|
63
|
+
[AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.ABORT_TURN]: [AGENT_RUNTIME_REMOTE_CONTROL_APPROVAL_ID],
|
|
64
|
+
[AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.ELEVATE_AUTONOMY]: [
|
|
65
|
+
AGENT_RUNTIME_REMOTE_CONTROL_APPROVAL_ID,
|
|
66
|
+
],
|
|
67
|
+
[AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.LOWER_AUTONOMY]: [],
|
|
68
|
+
[AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.APPROVE_INFLIGHT]: [],
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Deterministic mock session identifier returned by tracer-bullet
|
|
73
|
+
* implementations. The real impl in WU-2733 reads the session from the
|
|
74
|
+
* input payload; the mock returns a fixed value so round-trip tests
|
|
75
|
+
* pin the wire contract without leaking runtime state.
|
|
76
|
+
*/
|
|
77
|
+
export const MOCK_SESSION_ID = 'agent-runtime-remote-control-mock-session' as const;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Shared result shape for the five non-abort remote controls. Real
|
|
81
|
+
* impls may extend this — the additive-only rule lets WU-2733 land
|
|
82
|
+
* without contract drift.
|
|
83
|
+
*/
|
|
84
|
+
export interface RemoteControlInvocationResult {
|
|
85
|
+
status: 'ok';
|
|
86
|
+
tool_name: AgentRuntimeRemoteControlToolName;
|
|
87
|
+
session_id: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* abort_turn carries the ADR-013 §1 abort contract: cleanup_status +
|
|
92
|
+
* recovery_action. Cloud renders recovery_action as a hint; NEVER
|
|
93
|
+
* auto-executes (see ADR-013 §1 safety lock).
|
|
94
|
+
*/
|
|
95
|
+
export interface AbortTurnInvocationResult {
|
|
96
|
+
status: 'ok';
|
|
97
|
+
tool_name: typeof AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.ABORT_TURN;
|
|
98
|
+
session_id: string;
|
|
99
|
+
cleanup_status: AgentRuntimeCleanupStatus;
|
|
100
|
+
recovery_action: string | null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export type AnyRemoteControlInvocationResult =
|
|
104
|
+
| RemoteControlInvocationResult
|
|
105
|
+
| AbortTurnInvocationResult;
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
// Copyright (c) 2026 Hellmai Ltd
|
|
2
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Session Schema (WU-2754, ADR-014 extension 2)
|
|
6
|
+
*
|
|
7
|
+
* Canonical schema for agent session records persisted under
|
|
8
|
+
* `.lumenflow/sessions/<WU>.json` and the `current.json` compatibility pointer.
|
|
9
|
+
*
|
|
10
|
+
* ADR-014 extension 2 adds three optional axes:
|
|
11
|
+
* - `lifecycle_role` — what phase of delivery the session owns
|
|
12
|
+
* (opaque string at the kernel layer; pack owns vocabulary)
|
|
13
|
+
* - `specialty_profile` — which skill bundle the agent self-identifies as carrying
|
|
14
|
+
* - `delegation_id` — reference back to the parent delegation registry record
|
|
15
|
+
* (dlg-XXXX) so `delegation:list` can cross-display session
|
|
16
|
+
* axes alongside requested_role / actual_role.
|
|
17
|
+
*
|
|
18
|
+
* Schema-version discipline:
|
|
19
|
+
* - v1 legacy records have NO `schemaVersion` field and may omit the three
|
|
20
|
+
* new axes entirely. Readers MUST accept them without error.
|
|
21
|
+
* - v2 records stamp `schemaVersion: 'v2'`. New writes default to v2 and
|
|
22
|
+
* populate `lifecycle_role` + `specialty_profile` (sensible pack-layer
|
|
23
|
+
* defaults supplied by callers). Kernel-agnostic: the schema itself
|
|
24
|
+
* carries opaque strings; the pack (@lumenflow/packs/software-delivery)
|
|
25
|
+
* owns the canonical vocabulary.
|
|
26
|
+
*
|
|
27
|
+
* Reference: docs/09-architecture-decisions/ADR-014-typed-agent-role-contract.md §181
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
31
|
+
import path from 'node:path';
|
|
32
|
+
import { parse as parseYAML } from 'yaml';
|
|
33
|
+
import { z } from 'zod';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Session schema version discriminator.
|
|
37
|
+
*
|
|
38
|
+
* v1 = legacy (no stamp). v2 = ADR-014 extension 2 (may carry role axes).
|
|
39
|
+
*/
|
|
40
|
+
export const SESSION_SCHEMA_VERSION_V2 = 'v2' as const;
|
|
41
|
+
|
|
42
|
+
/** Known session schema versions (extensible by additive pack updates). */
|
|
43
|
+
export const SESSION_SCHEMA_VERSIONS = [SESSION_SCHEMA_VERSION_V2] as const;
|
|
44
|
+
|
|
45
|
+
/** Type union for session schema version values. */
|
|
46
|
+
export type SessionSchemaVersion = (typeof SESSION_SCHEMA_VERSIONS)[number];
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Default ADR-014 axes applied when a v2 session is written without explicit
|
|
50
|
+
* caller-supplied values. Kernel-agnostic defaults — the pack may override via
|
|
51
|
+
* `resolveDefaultRoleContract` (CLI delegation-role-resolver.ts).
|
|
52
|
+
*/
|
|
53
|
+
export const DEFAULT_SESSION_LIFECYCLE_ROLE = 'executor';
|
|
54
|
+
export const DEFAULT_SESSION_SPECIALTY_PROFILE = 'general';
|
|
55
|
+
export const DEFAULT_DISPLAY_NAME_POOL = [
|
|
56
|
+
'Planck',
|
|
57
|
+
'Hegel',
|
|
58
|
+
'Curie',
|
|
59
|
+
'Noether',
|
|
60
|
+
'Turing',
|
|
61
|
+
'Ada',
|
|
62
|
+
'Gauss',
|
|
63
|
+
'Euler',
|
|
64
|
+
'Raman',
|
|
65
|
+
'Hopper',
|
|
66
|
+
'Lovelace',
|
|
67
|
+
'Shannon',
|
|
68
|
+
] as const;
|
|
69
|
+
export const DISPLAY_NAME_POOL_RELATIVE_PATH = '.lumenflow/agents/display-name-pool.yaml';
|
|
70
|
+
export const DISPLAY_NAME_RESERVATIONS_RELATIVE_PATH =
|
|
71
|
+
'.lumenflow/state/display-name-reservations.jsonl';
|
|
72
|
+
export const DISPLAY_NAME_RESERVATION_TTL_HOURS = 12;
|
|
73
|
+
|
|
74
|
+
/** Regex for delegation_id references back into delegation registry. */
|
|
75
|
+
export const DELEGATION_ID_PATTERN = /^dlg-[0-9a-f]{4}$/;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Session role axes — the three ADR-014 extension 2 fields that bind a live
|
|
79
|
+
* session to its delegation parent.
|
|
80
|
+
*/
|
|
81
|
+
export const SessionRoleAxesSchema = z.object({
|
|
82
|
+
lifecycle_role: z.string().min(1, { message: 'lifecycle_role must be non-empty' }).optional(),
|
|
83
|
+
specialty_profile: z
|
|
84
|
+
.string()
|
|
85
|
+
.min(1, { message: 'specialty_profile must be non-empty' })
|
|
86
|
+
.optional(),
|
|
87
|
+
display_name: z.string().min(1, { message: 'display_name must be non-empty' }).optional(),
|
|
88
|
+
delegation_id: z
|
|
89
|
+
.string()
|
|
90
|
+
.regex(DELEGATION_ID_PATTERN, {
|
|
91
|
+
message: 'delegation_id must match dlg-XXXX (lowercase hex)',
|
|
92
|
+
})
|
|
93
|
+
.optional(),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Base session schema — shared fields across v1 and v2.
|
|
98
|
+
*
|
|
99
|
+
* Intentionally permissive: existing on-disk records have grown organically
|
|
100
|
+
* (lane, incidents, tiers, etc.). This schema focuses on the ADR-014 axes
|
|
101
|
+
* and only asserts the minimum invariants required for cross-linkage.
|
|
102
|
+
*/
|
|
103
|
+
const SessionRecordBaseSchema = z.object({
|
|
104
|
+
session_id: z.string().min(1),
|
|
105
|
+
wu_id: z.string().min(1),
|
|
106
|
+
started: z.string().min(1),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* v2 session record — ADR-014 axes are optional at the schema level because
|
|
111
|
+
* existing callers may not populate them yet. However, the auto-integration
|
|
112
|
+
* layer defaults them on every new v2 write (see auto-session-integration.ts).
|
|
113
|
+
*/
|
|
114
|
+
const SessionRecordV2Schema = SessionRecordBaseSchema.extend({
|
|
115
|
+
schemaVersion: z.literal(SESSION_SCHEMA_VERSION_V2),
|
|
116
|
+
lifecycle_role: z.string().min(1).optional(),
|
|
117
|
+
specialty_profile: z.string().min(1).optional(),
|
|
118
|
+
display_name: z.string().min(1).optional(),
|
|
119
|
+
delegation_id: z
|
|
120
|
+
.string()
|
|
121
|
+
.regex(DELEGATION_ID_PATTERN, {
|
|
122
|
+
message: 'delegation_id must match dlg-XXXX (lowercase hex)',
|
|
123
|
+
})
|
|
124
|
+
.optional(),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* v1-compat session record — no `schemaVersion` stamp. ADR-014 axes may be
|
|
129
|
+
* present (lenient) but are not required.
|
|
130
|
+
*/
|
|
131
|
+
const SessionRecordV1Schema = SessionRecordBaseSchema.extend({
|
|
132
|
+
schemaVersion: z.undefined().optional(),
|
|
133
|
+
lifecycle_role: z.string().min(1).optional(),
|
|
134
|
+
specialty_profile: z.string().min(1).optional(),
|
|
135
|
+
display_name: z.string().min(1).optional(),
|
|
136
|
+
delegation_id: z.string().optional(),
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Discriminated union — both v1 and v2 records are accepted on read.
|
|
141
|
+
*/
|
|
142
|
+
export const SessionRecordSchema = z.union([SessionRecordV2Schema, SessionRecordV1Schema]);
|
|
143
|
+
|
|
144
|
+
export type SessionRecordV1 = z.infer<typeof SessionRecordV1Schema>;
|
|
145
|
+
export type SessionRecordV2 = z.infer<typeof SessionRecordV2Schema>;
|
|
146
|
+
export type SessionRecord = z.infer<typeof SessionRecordSchema>;
|
|
147
|
+
export type SessionRoleAxes = z.infer<typeof SessionRoleAxesSchema>;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Validates a session record against the v1/v2 discriminated union.
|
|
151
|
+
* Accepts both legacy and v2 records; callers that want strict v2 validation
|
|
152
|
+
* should check `isV2SessionRecord` after validation.
|
|
153
|
+
*/
|
|
154
|
+
export function validateSessionRecord(data: unknown) {
|
|
155
|
+
return SessionRecordSchema.safeParse(data);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Type guard: returns true if the session record carries the v2 stamp.
|
|
160
|
+
*/
|
|
161
|
+
export function isV2SessionRecord(record: SessionRecord): record is SessionRecordV2 {
|
|
162
|
+
return record.schemaVersion === SESSION_SCHEMA_VERSION_V2;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Apply ADR-014 v2 defaults to a partial role-axes input. Kernel-agnostic:
|
|
167
|
+
* string values passed through as-is; the pack vocabulary (lifecycle_role +
|
|
168
|
+
* specialty_profile) is owned by CLI callers.
|
|
169
|
+
*/
|
|
170
|
+
export function withV2RoleDefaults(input: {
|
|
171
|
+
lifecycle_role?: string | null;
|
|
172
|
+
specialty_profile?: string | null;
|
|
173
|
+
display_name?: string | null;
|
|
174
|
+
delegation_id?: string | null;
|
|
175
|
+
}): {
|
|
176
|
+
lifecycle_role: string;
|
|
177
|
+
specialty_profile: string;
|
|
178
|
+
display_name?: string;
|
|
179
|
+
delegation_id?: string;
|
|
180
|
+
} {
|
|
181
|
+
const lifecycle_role =
|
|
182
|
+
typeof input.lifecycle_role === 'string' && input.lifecycle_role.trim().length > 0
|
|
183
|
+
? input.lifecycle_role.trim()
|
|
184
|
+
: DEFAULT_SESSION_LIFECYCLE_ROLE;
|
|
185
|
+
const specialty_profile =
|
|
186
|
+
typeof input.specialty_profile === 'string' && input.specialty_profile.trim().length > 0
|
|
187
|
+
? input.specialty_profile.trim()
|
|
188
|
+
: DEFAULT_SESSION_SPECIALTY_PROFILE;
|
|
189
|
+
const delegation_id =
|
|
190
|
+
typeof input.delegation_id === 'string' && DELEGATION_ID_PATTERN.test(input.delegation_id)
|
|
191
|
+
? input.delegation_id
|
|
192
|
+
: undefined;
|
|
193
|
+
const display_name =
|
|
194
|
+
typeof input.display_name === 'string' && input.display_name.trim().length > 0
|
|
195
|
+
? input.display_name.trim()
|
|
196
|
+
: undefined;
|
|
197
|
+
const out: {
|
|
198
|
+
lifecycle_role: string;
|
|
199
|
+
specialty_profile: string;
|
|
200
|
+
display_name?: string;
|
|
201
|
+
delegation_id?: string;
|
|
202
|
+
} = {
|
|
203
|
+
lifecycle_role,
|
|
204
|
+
specialty_profile,
|
|
205
|
+
};
|
|
206
|
+
if (display_name) {
|
|
207
|
+
out.display_name = display_name;
|
|
208
|
+
}
|
|
209
|
+
if (delegation_id) {
|
|
210
|
+
out.delegation_id = delegation_id;
|
|
211
|
+
}
|
|
212
|
+
return out;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
interface DisplayNameReservation {
|
|
216
|
+
display_name: string;
|
|
217
|
+
session_id: string;
|
|
218
|
+
acquired_at: string;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
interface DisplayNamePoolDocument {
|
|
222
|
+
names?: string[];
|
|
223
|
+
reservations?: DisplayNameReservation[];
|
|
224
|
+
ttl_hours?: number;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function getDisplayNamePoolPath(workspaceRoot: string): string {
|
|
228
|
+
return path.join(workspaceRoot, DISPLAY_NAME_POOL_RELATIVE_PATH);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function getDisplayNameReservationsPath(workspaceRoot: string): string {
|
|
232
|
+
return path.join(workspaceRoot, DISPLAY_NAME_RESERVATIONS_RELATIVE_PATH);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function getDisplayNameReservationTtlMs(ttlHours: number | undefined): number {
|
|
236
|
+
const hours =
|
|
237
|
+
typeof ttlHours === 'number' && Number.isFinite(ttlHours) && ttlHours > 0
|
|
238
|
+
? ttlHours
|
|
239
|
+
: DISPLAY_NAME_RESERVATION_TTL_HOURS;
|
|
240
|
+
return hours * 60 * 60 * 1000;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function normalizeDisplayNamePoolDocument(raw: unknown): DisplayNamePoolDocument {
|
|
244
|
+
if (typeof raw !== 'object' || raw === null) {
|
|
245
|
+
return {
|
|
246
|
+
names: [...DEFAULT_DISPLAY_NAME_POOL],
|
|
247
|
+
reservations: [],
|
|
248
|
+
ttl_hours: DISPLAY_NAME_RESERVATION_TTL_HOURS,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const candidate = raw as DisplayNamePoolDocument;
|
|
253
|
+
return {
|
|
254
|
+
names: Array.isArray(candidate.names)
|
|
255
|
+
? candidate.names.filter(
|
|
256
|
+
(entry): entry is string => typeof entry === 'string' && entry.length > 0,
|
|
257
|
+
)
|
|
258
|
+
: [...DEFAULT_DISPLAY_NAME_POOL],
|
|
259
|
+
reservations: Array.isArray(candidate.reservations)
|
|
260
|
+
? candidate.reservations.filter(
|
|
261
|
+
(entry): entry is DisplayNameReservation =>
|
|
262
|
+
typeof entry?.display_name === 'string' &&
|
|
263
|
+
entry.display_name.length > 0 &&
|
|
264
|
+
typeof entry?.session_id === 'string' &&
|
|
265
|
+
entry.session_id.length > 0 &&
|
|
266
|
+
typeof entry?.acquired_at === 'string' &&
|
|
267
|
+
entry.acquired_at.length > 0,
|
|
268
|
+
)
|
|
269
|
+
: [],
|
|
270
|
+
ttl_hours:
|
|
271
|
+
typeof candidate.ttl_hours === 'number' && Number.isFinite(candidate.ttl_hours)
|
|
272
|
+
? candidate.ttl_hours
|
|
273
|
+
: DISPLAY_NAME_RESERVATION_TTL_HOURS,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function readDisplayNamePoolDocument(workspaceRoot: string): DisplayNamePoolDocument {
|
|
278
|
+
const poolPath = getDisplayNamePoolPath(workspaceRoot);
|
|
279
|
+
if (!existsSync(poolPath)) {
|
|
280
|
+
return {
|
|
281
|
+
names: [...DEFAULT_DISPLAY_NAME_POOL],
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
return normalizeDisplayNamePoolDocument(
|
|
287
|
+
parseYAML(readFileSync(poolPath, { encoding: 'utf-8' })),
|
|
288
|
+
);
|
|
289
|
+
} catch {
|
|
290
|
+
return {
|
|
291
|
+
names: [...DEFAULT_DISPLAY_NAME_POOL],
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function readDisplayNameReservations(workspaceRoot: string): DisplayNameReservation[] {
|
|
297
|
+
const reservationsPath = getDisplayNameReservationsPath(workspaceRoot);
|
|
298
|
+
if (!existsSync(reservationsPath)) {
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const raw = readFileSync(reservationsPath, { encoding: 'utf-8' }).trim();
|
|
303
|
+
if (raw.length === 0) {
|
|
304
|
+
return [];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return raw
|
|
308
|
+
.split('\n')
|
|
309
|
+
.map((line) => {
|
|
310
|
+
try {
|
|
311
|
+
return JSON.parse(line) as DisplayNameReservation;
|
|
312
|
+
} catch {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
})
|
|
316
|
+
.filter(
|
|
317
|
+
(entry): entry is DisplayNameReservation =>
|
|
318
|
+
entry !== null &&
|
|
319
|
+
typeof entry.display_name === 'string' &&
|
|
320
|
+
entry.display_name.length > 0 &&
|
|
321
|
+
typeof entry.session_id === 'string' &&
|
|
322
|
+
entry.session_id.length > 0 &&
|
|
323
|
+
typeof entry.acquired_at === 'string' &&
|
|
324
|
+
entry.acquired_at.length > 0,
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function writeDisplayNameReservations(
|
|
329
|
+
workspaceRoot: string,
|
|
330
|
+
reservations: DisplayNameReservation[],
|
|
331
|
+
): void {
|
|
332
|
+
const reservationsPath = getDisplayNameReservationsPath(workspaceRoot);
|
|
333
|
+
if (reservations.length === 0) {
|
|
334
|
+
rmSync(reservationsPath, { force: true });
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
mkdirSync(path.dirname(reservationsPath), { recursive: true });
|
|
339
|
+
writeFileSync(
|
|
340
|
+
reservationsPath,
|
|
341
|
+
`${reservations.map((reservation) => JSON.stringify(reservation)).join('\n')}\n`,
|
|
342
|
+
{ encoding: 'utf-8' },
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function loadDisplayNameState(
|
|
347
|
+
workspaceRoot: string,
|
|
348
|
+
now: Date,
|
|
349
|
+
): {
|
|
350
|
+
names: string[];
|
|
351
|
+
reservations: DisplayNameReservation[];
|
|
352
|
+
} {
|
|
353
|
+
const document = readDisplayNamePoolDocument(workspaceRoot);
|
|
354
|
+
const names =
|
|
355
|
+
document.names && document.names.length > 0 ? document.names : [...DEFAULT_DISPLAY_NAME_POOL];
|
|
356
|
+
const ttlMs = getDisplayNameReservationTtlMs(document.ttl_hours);
|
|
357
|
+
const reservations = pruneExpiredReservations(
|
|
358
|
+
[...readDisplayNameReservations(workspaceRoot), ...(document.reservations ?? [])],
|
|
359
|
+
now,
|
|
360
|
+
ttlMs,
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
const uniqueReservations = new Map<string, DisplayNameReservation>();
|
|
364
|
+
for (const reservation of reservations) {
|
|
365
|
+
uniqueReservations.set(reservation.session_id, reservation);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
writeDisplayNameReservations(workspaceRoot, [...uniqueReservations.values()]);
|
|
369
|
+
|
|
370
|
+
return {
|
|
371
|
+
names,
|
|
372
|
+
reservations: [...uniqueReservations.values()],
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function pruneExpiredReservations(
|
|
377
|
+
reservations: DisplayNameReservation[],
|
|
378
|
+
now: Date,
|
|
379
|
+
ttlMs: number,
|
|
380
|
+
): DisplayNameReservation[] {
|
|
381
|
+
return reservations.filter((reservation) => {
|
|
382
|
+
const acquiredAt = Date.parse(reservation.acquired_at);
|
|
383
|
+
if (Number.isNaN(acquiredAt)) {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
return now.getTime() - acquiredAt < ttlMs;
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export function acquireDisplayName(
|
|
391
|
+
workspaceRoot: string,
|
|
392
|
+
sessionId: string,
|
|
393
|
+
now: Date = new Date(),
|
|
394
|
+
): string {
|
|
395
|
+
const state = loadDisplayNameState(workspaceRoot, now);
|
|
396
|
+
const reservations = state.reservations.filter(
|
|
397
|
+
(reservation) => reservation.session_id !== sessionId,
|
|
398
|
+
);
|
|
399
|
+
const reservedNames = new Set(reservations.map((reservation) => reservation.display_name));
|
|
400
|
+
const selectedName = state.names.find((name) => !reservedNames.has(name)) ?? sessionId;
|
|
401
|
+
|
|
402
|
+
reservations.push({
|
|
403
|
+
display_name: selectedName,
|
|
404
|
+
session_id: sessionId,
|
|
405
|
+
acquired_at: now.toISOString(),
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
writeDisplayNameReservations(workspaceRoot, reservations);
|
|
409
|
+
|
|
410
|
+
return selectedName;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export function releaseDisplayName(
|
|
414
|
+
workspaceRoot: string,
|
|
415
|
+
sessionId: string,
|
|
416
|
+
now: Date = new Date(),
|
|
417
|
+
): void {
|
|
418
|
+
const state = loadDisplayNameState(workspaceRoot, now);
|
|
419
|
+
const reservations = state.reservations.filter(
|
|
420
|
+
(reservation) => reservation.session_id !== sessionId,
|
|
421
|
+
);
|
|
422
|
+
writeDisplayNameReservations(workspaceRoot, reservations);
|
|
423
|
+
}
|