@lumenflow/cli 5.1.0 → 5.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capacity-snapshot-emitter.js +10 -12
- package/dist/capacity-snapshot-emitter.js.map +1 -1
- package/dist/docs-generate-pack-reference.js +54 -0
- package/dist/docs-generate-pack-reference.js.map +1 -1
- package/dist/flow-bottlenecks.js +55 -0
- package/dist/flow-bottlenecks.js.map +1 -1
- package/dist/kernel-event-sync/lifecycle-emitters.js +13 -14
- package/dist/kernel-event-sync/lifecycle-emitters.js.map +1 -1
- package/dist/kernel-event-sync/narrow-emissions.js +7 -4
- package/dist/kernel-event-sync/narrow-emissions.js.map +1 -1
- package/dist/kernel-event-sync/software-delivery-emitters.js +91 -0
- package/dist/kernel-event-sync/software-delivery-emitters.js.map +1 -1
- package/dist/lumenflow-upgrade.js +39 -10
- package/dist/lumenflow-upgrade.js.map +1 -1
- package/dist/metrics-snapshot.js +51 -0
- package/dist/metrics-snapshot.js.map +1 -1
- package/dist/orchestrate-init-status.js +24 -11
- package/dist/orchestrate-init-status.js.map +1 -1
- package/dist/pack-install.js +27 -20
- package/dist/pack-install.js.map +1 -1
- package/dist/pack-publish.js +34 -26
- package/dist/pack-publish.js.map +1 -1
- package/dist/release.js +308 -149
- package/dist/release.js.map +1 -1
- package/dist/strict-progress.js +49 -0
- package/dist/strict-progress.js.map +1 -1
- package/dist/temp-dir-cleanup.js +112 -0
- package/dist/temp-dir-cleanup.js.map +1 -0
- package/dist/validate-agent-sync.js +6 -5
- package/dist/validate-agent-sync.js.map +1 -1
- package/dist/wu-escalate.js +33 -0
- package/dist/wu-escalate.js.map +1 -1
- package/dist/wu-preflight.js +28 -0
- package/dist/wu-preflight.js.map +1 -1
- package/dist/wu-recover.js +35 -0
- package/dist/wu-recover.js.map +1 -1
- package/dist/wu-spawn-strategy-resolver.js +4 -0
- package/dist/wu-spawn-strategy-resolver.js.map +1 -1
- package/dist/wu-validate.js +63 -0
- package/dist/wu-validate.js.map +1 -1
- package/package.json +11 -11
- package/packs/agent-runtime/manifest.ts +55 -4
- package/packs/agent-runtime/manifest.yaml +16 -6
- package/packs/agent-runtime/orchestration.ts +26 -1
- package/packs/agent-runtime/package.json +1 -1
- package/packs/agent-runtime/remote-controls/operations.ts +6 -0
- package/packs/agent-runtime/tool-impl/remote-controls.mock.ts +4 -0
- package/packs/agent-runtime/turn-lifecycle-events.ts +90 -1
- package/packs/sidekick/manifest.yaml +6 -0
- package/packs/sidekick/package.json +2 -1
- package/packs/sidekick/sidekick-events.ts +195 -18
- package/packs/sidekick/src/adapters/control-plane-bridge.adapter.ts +18 -10
- package/packs/sidekick/src/adapters/filesystem-bridge.adapter.ts +4 -0
- package/packs/sidekick/src/domain/channel.types.ts +34 -54
- package/packs/sidekick/src/ports/channel-bridge.port.ts +29 -12
- package/packs/sidekick/tool-impl/channel-tools.ts +47 -16
- package/packs/sidekick/tool-impl/system-tools.ts +4 -6
- package/packs/software-delivery/manifest.ts +94 -7
- package/packs/software-delivery/manifest.yaml +42 -5
- package/packs/software-delivery/package.json +1 -1
- package/packs/software-delivery/tool-impl/wu-lifecycle-tools.ts +30 -0
|
@@ -17,6 +17,9 @@ import {
|
|
|
17
17
|
AGENT_RUNTIME_AGENT_WORKFLOW_NODE_ID_METADATA_KEY,
|
|
18
18
|
} from './constants.js';
|
|
19
19
|
import {
|
|
20
|
+
AGENT_RUNTIME_BUDGET_LEVELS,
|
|
21
|
+
AGENT_RUNTIME_BUDGET_SCOPES,
|
|
22
|
+
AGENT_RUNTIME_TURN_COMPLETED_STATUSES,
|
|
20
23
|
buildApprovalRequiredEvent,
|
|
21
24
|
buildBudgetThresholdEvent,
|
|
22
25
|
buildScheduledWakeupSetEvent,
|
|
@@ -1017,7 +1020,12 @@ async function runGovernedAgentLoopInternal(
|
|
|
1017
1020
|
buildTurnCompletedEvent({
|
|
1018
1021
|
session_id: sessionId,
|
|
1019
1022
|
turn_index: currentTurnIndex,
|
|
1020
|
-
|
|
1023
|
+
// WU-2829: TurnCompletedEvent is only emitted after execute-turn
|
|
1024
|
+
// returns a normalized, successful turn output — every reachable
|
|
1025
|
+
// emission here represents a SUCCESS lifecycle outcome. The
|
|
1026
|
+
// execute-turn-failure, normalization-failure, and tool-failure
|
|
1027
|
+
// branches emit TurnAbortedEvent instead (see earlier branches).
|
|
1028
|
+
status: AGENT_RUNTIME_TURN_COMPLETED_STATUSES.SUCCESS,
|
|
1021
1029
|
cost_breakdown: createZeroTurnCostBreakdown(),
|
|
1022
1030
|
}),
|
|
1023
1031
|
);
|
|
@@ -1077,6 +1085,12 @@ async function runGovernedAgentLoopInternal(
|
|
|
1077
1085
|
historyEntry.tool_call_id = toolCallId;
|
|
1078
1086
|
historyEntry.tool_output = toolOutput;
|
|
1079
1087
|
|
|
1088
|
+
// WU-2829: approved=false when the tool-host blocked pending
|
|
1089
|
+
// approval (a paired APPROVAL_REQUIRED event follows). Every other
|
|
1090
|
+
// reachable branch means the call proceeded, so approved=true.
|
|
1091
|
+
const toolCallApproved = !(
|
|
1092
|
+
!toolOutput.success && toolOutput.error?.code === TOOL_ERROR_CODES.APPROVAL_REQUIRED
|
|
1093
|
+
);
|
|
1080
1094
|
emitAgentRuntimeEvent(
|
|
1081
1095
|
input.eventSink,
|
|
1082
1096
|
buildToolCalledEvent({
|
|
@@ -1084,6 +1098,7 @@ async function runGovernedAgentLoopInternal(
|
|
|
1084
1098
|
turn_index: currentTurnIndex,
|
|
1085
1099
|
tool_name: normalizedTurn.requested_tool.name,
|
|
1086
1100
|
tool_call_id: toolCallId,
|
|
1101
|
+
approved: toolCallApproved,
|
|
1087
1102
|
}),
|
|
1088
1103
|
);
|
|
1089
1104
|
|
|
@@ -1126,6 +1141,16 @@ async function runGovernedAgentLoopInternal(
|
|
|
1126
1141
|
budget_name: 'max_turns_per_invocation',
|
|
1127
1142
|
threshold: maxTurns,
|
|
1128
1143
|
observed_value: invocationTurnCount,
|
|
1144
|
+
// WU-2829: we reach this emission only after the invocation turn
|
|
1145
|
+
// budget is saturated — level is EXCEEDED, remaining is 0, and
|
|
1146
|
+
// the scope is the session that ran out of turn budget. This
|
|
1147
|
+
// budget is in turns, not USD; remaining_usd mirrors "0 headroom
|
|
1148
|
+
// in the counted unit" and cloud renders the context via
|
|
1149
|
+
// budget_name.
|
|
1150
|
+
level: AGENT_RUNTIME_BUDGET_LEVELS.EXCEEDED,
|
|
1151
|
+
remaining_usd: 0,
|
|
1152
|
+
scope: AGENT_RUNTIME_BUDGET_SCOPES.SESSION,
|
|
1153
|
+
scope_id: sessionId,
|
|
1129
1154
|
}),
|
|
1130
1155
|
);
|
|
1131
1156
|
|
|
@@ -70,6 +70,12 @@ function emitToolCalled(
|
|
|
70
70
|
turn_index: REMOTE_CONTROL_TURN_INDEX,
|
|
71
71
|
tool_name: toolName,
|
|
72
72
|
tool_call_id: randomUUID(),
|
|
73
|
+
// WU-2829: remote-control invocations are governed by the
|
|
74
|
+
// conductor's approval flow BEFORE the kernel-side operation
|
|
75
|
+
// runs. By the time this emission fires, the operation has
|
|
76
|
+
// already been authorised — approved is always true at this
|
|
77
|
+
// call site.
|
|
78
|
+
approved: true,
|
|
73
79
|
}),
|
|
74
80
|
);
|
|
75
81
|
}
|
|
@@ -111,6 +111,10 @@ export class MockRemoteControlAdapter implements RemoteControlPort {
|
|
|
111
111
|
turn_index: MOCK_TURN_INDEX,
|
|
112
112
|
tool_name: toolName,
|
|
113
113
|
tool_call_id: randomUUID(),
|
|
114
|
+
// WU-2829: mock fixture mirrors the real-adapter contract —
|
|
115
|
+
// remote-control operations emit tool_called after the
|
|
116
|
+
// conductor's approval flow has authorised the invocation.
|
|
117
|
+
approved: true,
|
|
114
118
|
}),
|
|
115
119
|
);
|
|
116
120
|
}
|
|
@@ -69,6 +69,62 @@ export interface AgentRuntimeTurnCostBreakdown {
|
|
|
69
69
|
|
|
70
70
|
export type AgentRuntimeCleanupStatus = 'clean' | 'partial' | 'needs_recovery';
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* WU-2829 (INIT-062 WU-C): narrowed TurnCompletedEvent.status so cloud
|
|
74
|
+
* can resolve the turn discriminant without runtime string checks. The
|
|
75
|
+
* four values describe the lifecycle outcome of a turn; the internal
|
|
76
|
+
* per-turn semantic status (`reply | tool_request | complete | escalate`)
|
|
77
|
+
* remains on AgentRuntimeExecuteTurnOutput — it is NOT the same concept.
|
|
78
|
+
*/
|
|
79
|
+
export const AGENT_RUNTIME_TURN_COMPLETED_STATUSES = {
|
|
80
|
+
SUCCESS: 'success',
|
|
81
|
+
ERROR: 'error',
|
|
82
|
+
ABORTED: 'aborted',
|
|
83
|
+
PAUSED: 'paused',
|
|
84
|
+
} as const;
|
|
85
|
+
|
|
86
|
+
export const AGENT_RUNTIME_TURN_COMPLETED_STATUS_VALUES = [
|
|
87
|
+
AGENT_RUNTIME_TURN_COMPLETED_STATUSES.SUCCESS,
|
|
88
|
+
AGENT_RUNTIME_TURN_COMPLETED_STATUSES.ERROR,
|
|
89
|
+
AGENT_RUNTIME_TURN_COMPLETED_STATUSES.ABORTED,
|
|
90
|
+
AGENT_RUNTIME_TURN_COMPLETED_STATUSES.PAUSED,
|
|
91
|
+
] as const;
|
|
92
|
+
|
|
93
|
+
export type AgentRuntimeTurnCompletedStatus =
|
|
94
|
+
(typeof AGENT_RUNTIME_TURN_COMPLETED_STATUS_VALUES)[number];
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* WU-2829 (INIT-062 WU-C): budget threshold enrichment. Cloud previously
|
|
98
|
+
* had to reconstruct `level` from `threshold`/`observed_value`; we now
|
|
99
|
+
* supply the discriminant directly alongside `remaining_usd`, `scope`,
|
|
100
|
+
* and `scope_id` so dashboards render without post-hoc inference.
|
|
101
|
+
*/
|
|
102
|
+
export const AGENT_RUNTIME_BUDGET_LEVELS = {
|
|
103
|
+
WARNING: 'warning',
|
|
104
|
+
EXCEEDED: 'exceeded',
|
|
105
|
+
} as const;
|
|
106
|
+
|
|
107
|
+
export const AGENT_RUNTIME_BUDGET_LEVEL_VALUES = [
|
|
108
|
+
AGENT_RUNTIME_BUDGET_LEVELS.WARNING,
|
|
109
|
+
AGENT_RUNTIME_BUDGET_LEVELS.EXCEEDED,
|
|
110
|
+
] as const;
|
|
111
|
+
|
|
112
|
+
export type AgentRuntimeBudgetLevel = (typeof AGENT_RUNTIME_BUDGET_LEVEL_VALUES)[number];
|
|
113
|
+
|
|
114
|
+
export const AGENT_RUNTIME_BUDGET_SCOPES = {
|
|
115
|
+
SESSION: 'session',
|
|
116
|
+
WORKSPACE: 'workspace',
|
|
117
|
+
ORG: 'org',
|
|
118
|
+
} as const;
|
|
119
|
+
|
|
120
|
+
export const AGENT_RUNTIME_BUDGET_SCOPE_VALUES = [
|
|
121
|
+
AGENT_RUNTIME_BUDGET_SCOPES.SESSION,
|
|
122
|
+
AGENT_RUNTIME_BUDGET_SCOPES.WORKSPACE,
|
|
123
|
+
AGENT_RUNTIME_BUDGET_SCOPES.ORG,
|
|
124
|
+
] as const;
|
|
125
|
+
|
|
126
|
+
export type AgentRuntimeBudgetScope = (typeof AGENT_RUNTIME_BUDGET_SCOPE_VALUES)[number];
|
|
127
|
+
|
|
72
128
|
export interface TurnStartedEvent extends AgentRuntimeEventEnvelope {
|
|
73
129
|
kind: typeof AGENT_RUNTIME_EVENT_KINDS.TURN_STARTED;
|
|
74
130
|
session_id: string;
|
|
@@ -80,7 +136,9 @@ export interface TurnCompletedEvent extends AgentRuntimeEventEnvelope {
|
|
|
80
136
|
kind: typeof AGENT_RUNTIME_EVENT_KINDS.TURN_COMPLETED;
|
|
81
137
|
session_id: string;
|
|
82
138
|
turn_index: number;
|
|
83
|
-
|
|
139
|
+
// WU-2829: narrowed from `string` to the four lifecycle outcomes so
|
|
140
|
+
// cloud gets TS exhaustiveness on the discriminated union.
|
|
141
|
+
status: AgentRuntimeTurnCompletedStatus;
|
|
84
142
|
cost_breakdown: AgentRuntimeTurnCostBreakdown;
|
|
85
143
|
}
|
|
86
144
|
|
|
@@ -99,6 +157,15 @@ export interface ToolCalledEvent extends AgentRuntimeEventEnvelope {
|
|
|
99
157
|
turn_index: number;
|
|
100
158
|
tool_name: string;
|
|
101
159
|
tool_call_id: string;
|
|
160
|
+
/**
|
|
161
|
+
* WU-2829 (INIT-062 WU-C): approval discriminant so cloud no longer
|
|
162
|
+
* has to cross-reference APPROVAL_REQUIRED events to tell which tool
|
|
163
|
+
* calls were gated. `true` = call proceeded (either no approval
|
|
164
|
+
* required, or approval was satisfied); `false` = call blocked
|
|
165
|
+
* pending an approval (paired with a subsequent APPROVAL_REQUIRED
|
|
166
|
+
* event carrying the request_id).
|
|
167
|
+
*/
|
|
168
|
+
approved: boolean;
|
|
102
169
|
}
|
|
103
170
|
|
|
104
171
|
export interface ApprovalRequiredEvent extends AgentRuntimeEventEnvelope {
|
|
@@ -149,6 +216,28 @@ export interface BudgetThresholdEvent extends AgentRuntimeEventEnvelope {
|
|
|
149
216
|
budget_name: string;
|
|
150
217
|
threshold: number;
|
|
151
218
|
observed_value: number;
|
|
219
|
+
/**
|
|
220
|
+
* WU-2829 (INIT-062 WU-C): discriminant for cloud dashboards so the
|
|
221
|
+
* level is no longer inferred from threshold/observed_value.
|
|
222
|
+
*/
|
|
223
|
+
level: AgentRuntimeBudgetLevel;
|
|
224
|
+
/**
|
|
225
|
+
* WU-2829 (INIT-062 WU-C): remaining USD headroom for the budget at
|
|
226
|
+
* emission time. Negative values are permitted when the budget is
|
|
227
|
+
* already exceeded.
|
|
228
|
+
*/
|
|
229
|
+
remaining_usd: number;
|
|
230
|
+
/**
|
|
231
|
+
* WU-2829 (INIT-062 WU-C): budget scope so cloud can route the signal
|
|
232
|
+
* to the correct dashboard (session / workspace / org).
|
|
233
|
+
*/
|
|
234
|
+
scope: AgentRuntimeBudgetScope;
|
|
235
|
+
/**
|
|
236
|
+
* WU-2829 (INIT-062 WU-C): identifier of the scope object (session id,
|
|
237
|
+
* workspace id, or org id) — paired with `scope` to pin the emission
|
|
238
|
+
* to a specific subject.
|
|
239
|
+
*/
|
|
240
|
+
scope_id: string;
|
|
152
241
|
}
|
|
153
242
|
|
|
154
243
|
export interface AgentSessionEnrolledEvent extends AgentRuntimeEventEnvelope {
|
|
@@ -678,6 +678,9 @@ emitted_event_kinds:
|
|
|
678
678
|
- sidekick:routine_executed
|
|
679
679
|
- sidekick:routine_step_failed
|
|
680
680
|
- sidekick:state_rehydrated
|
|
681
|
+
- sidekick:state_rehydration_started
|
|
682
|
+
- sidekick:state_rehydration_chunk
|
|
683
|
+
- sidekick:state_rehydration_completed
|
|
681
684
|
backpressure_policy:
|
|
682
685
|
sidekick:task_created: queue-with-replay
|
|
683
686
|
sidekick:task_completed: queue-with-replay
|
|
@@ -695,6 +698,9 @@ backpressure_policy:
|
|
|
695
698
|
sidekick:routine_executed: queue-with-replay
|
|
696
699
|
sidekick:routine_step_failed: queue-with-replay
|
|
697
700
|
sidekick:state_rehydrated: queue-with-replay
|
|
701
|
+
sidekick:state_rehydration_started: queue-with-replay
|
|
702
|
+
sidekick:state_rehydration_chunk: queue-with-replay
|
|
703
|
+
sidekick:state_rehydration_completed: queue-with-replay
|
|
698
704
|
state_aliases: {}
|
|
699
705
|
lane_templates: []
|
|
700
706
|
# WU-2735 (INIT-060 WU-7a, ADR-013 §ChannelBridge): the sidekick pack requires
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumenflow/packs-sidekick",
|
|
3
|
-
"version": "5.1
|
|
3
|
+
"version": "5.2.1",
|
|
4
4
|
"description": "Sidekick personal assistant pack for LumenFlow — 16 tools for task management, typed memory, channels, routines, and audit",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lumenflow",
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"typecheck": "tsc --noEmit"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
+
"@lumenflow/conductor-sdk": "workspace:^",
|
|
56
57
|
"@lumenflow/kernel": "workspace:^"
|
|
57
58
|
},
|
|
58
59
|
"devDependencies": {
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type MemoryRecord,
|
|
13
13
|
type RoutineRecord,
|
|
14
14
|
type SidekickStores,
|
|
15
|
+
type StoreName,
|
|
15
16
|
type TaskRecord,
|
|
16
17
|
} from './tool-impl/storage.js';
|
|
17
18
|
|
|
@@ -45,7 +46,17 @@ export const SIDEKICK_EVENT_KINDS = {
|
|
|
45
46
|
ROUTINE_COMMITTED: 'sidekick:routine_committed',
|
|
46
47
|
ROUTINE_EXECUTED: 'sidekick:routine_executed',
|
|
47
48
|
ROUTINE_STEP_FAILED: 'sidekick:routine_step_failed',
|
|
49
|
+
/**
|
|
50
|
+
* @deprecated WU-2830 (INIT-062 WU-D): `sidekick:state_rehydrated` is
|
|
51
|
+
* unbounded and cannot stream. New emitters MUST use the chunked trio
|
|
52
|
+
* (`STATE_REHYDRATION_STARTED`, `STATE_REHYDRATION_CHUNK`,
|
|
53
|
+
* `STATE_REHYDRATION_COMPLETED`). The constant and type are retained for
|
|
54
|
+
* backwards compatibility only.
|
|
55
|
+
*/
|
|
48
56
|
STATE_REHYDRATED: 'sidekick:state_rehydrated',
|
|
57
|
+
STATE_REHYDRATION_STARTED: 'sidekick:state_rehydration_started',
|
|
58
|
+
STATE_REHYDRATION_CHUNK: 'sidekick:state_rehydration_chunk',
|
|
59
|
+
STATE_REHYDRATION_COMPLETED: 'sidekick:state_rehydration_completed',
|
|
49
60
|
} as const;
|
|
50
61
|
|
|
51
62
|
export const SIDEKICK_EVENT_KIND_VALUES = [
|
|
@@ -65,6 +76,9 @@ export const SIDEKICK_EVENT_KIND_VALUES = [
|
|
|
65
76
|
SIDEKICK_EVENT_KINDS.ROUTINE_EXECUTED,
|
|
66
77
|
SIDEKICK_EVENT_KINDS.ROUTINE_STEP_FAILED,
|
|
67
78
|
SIDEKICK_EVENT_KINDS.STATE_REHYDRATED,
|
|
79
|
+
SIDEKICK_EVENT_KINDS.STATE_REHYDRATION_STARTED,
|
|
80
|
+
SIDEKICK_EVENT_KINDS.STATE_REHYDRATION_CHUNK,
|
|
81
|
+
SIDEKICK_EVENT_KINDS.STATE_REHYDRATION_COMPLETED,
|
|
68
82
|
] as const;
|
|
69
83
|
|
|
70
84
|
export type SidekickEventKind = (typeof SIDEKICK_EVENT_KIND_VALUES)[number];
|
|
@@ -93,6 +107,9 @@ export const SIDEKICK_EVENT_BACKPRESSURE_POLICY: Record<
|
|
|
93
107
|
[SIDEKICK_EVENT_KINDS.ROUTINE_EXECUTED]: QUEUE_WITH_REPLAY,
|
|
94
108
|
[SIDEKICK_EVENT_KINDS.ROUTINE_STEP_FAILED]: QUEUE_WITH_REPLAY,
|
|
95
109
|
[SIDEKICK_EVENT_KINDS.STATE_REHYDRATED]: QUEUE_WITH_REPLAY,
|
|
110
|
+
[SIDEKICK_EVENT_KINDS.STATE_REHYDRATION_STARTED]: QUEUE_WITH_REPLAY,
|
|
111
|
+
[SIDEKICK_EVENT_KINDS.STATE_REHYDRATION_CHUNK]: QUEUE_WITH_REPLAY,
|
|
112
|
+
[SIDEKICK_EVENT_KINDS.STATE_REHYDRATION_COMPLETED]: QUEUE_WITH_REPLAY,
|
|
96
113
|
};
|
|
97
114
|
|
|
98
115
|
interface SidekickEventEnvelope {
|
|
@@ -144,13 +161,23 @@ export interface MemoryForgottenEvent extends SidekickEventEnvelope {
|
|
|
144
161
|
|
|
145
162
|
export interface ChannelMessageSentEvent extends SidekickEventEnvelope {
|
|
146
163
|
kind: typeof SIDEKICK_EVENT_KINDS.CHANNEL_MESSAGE_SENT;
|
|
147
|
-
|
|
164
|
+
/**
|
|
165
|
+
* WU-2830 (INIT-062 WU-D): typed as {@link ChannelMessageRecord} for
|
|
166
|
+
* symmetry with {@link ChannelMessageReceivedEvent.messages} and to enable
|
|
167
|
+
* static analysis cloud-side.
|
|
168
|
+
*/
|
|
169
|
+
message: ChannelMessageRecord;
|
|
148
170
|
}
|
|
149
171
|
|
|
150
172
|
export interface ChannelMessageReceivedEvent extends SidekickEventEnvelope {
|
|
151
173
|
kind: typeof SIDEKICK_EVENT_KINDS.CHANNEL_MESSAGE_RECEIVED;
|
|
152
174
|
channel: string;
|
|
153
|
-
|
|
175
|
+
/**
|
|
176
|
+
* WU-2830 (INIT-062 WU-D): full {@link ChannelMessageRecord} payloads
|
|
177
|
+
* replace the previous bare `count` field. Subscribers count via
|
|
178
|
+
* `messages.length`; symmetric with {@link ChannelMessageSentEvent.message}.
|
|
179
|
+
*/
|
|
180
|
+
messages: ChannelMessageRecord[];
|
|
154
181
|
provider?: string;
|
|
155
182
|
}
|
|
156
183
|
|
|
@@ -199,11 +226,50 @@ export interface RoutineStepFailedEvent extends SidekickEventEnvelope {
|
|
|
199
226
|
routine_name?: string;
|
|
200
227
|
}
|
|
201
228
|
|
|
229
|
+
/**
|
|
230
|
+
* @deprecated WU-2830 (INIT-062 WU-D): unbounded snapshots cannot stream.
|
|
231
|
+
* Emit {@link StateRehydrationStartedEvent} +
|
|
232
|
+
* {@link StateRehydrationChunkEvent} (one per store) +
|
|
233
|
+
* {@link StateRehydrationCompletedEvent} instead. Retained for parity with
|
|
234
|
+
* existing consumers and the compile-time parity tests.
|
|
235
|
+
*/
|
|
202
236
|
export interface StateRehydratedEvent extends SidekickEventEnvelope {
|
|
203
237
|
kind: typeof SIDEKICK_EVENT_KINDS.STATE_REHYDRATED;
|
|
204
238
|
snapshot: SidekickStores;
|
|
205
239
|
}
|
|
206
240
|
|
|
241
|
+
export interface StateRehydrationStartedEvent extends SidekickEventEnvelope {
|
|
242
|
+
kind: typeof SIDEKICK_EVENT_KINDS.STATE_REHYDRATION_STARTED;
|
|
243
|
+
/** Correlation id shared by the started/chunk/completed trio. */
|
|
244
|
+
rehydration_id: string;
|
|
245
|
+
/** Total chunks the receiver should expect. */
|
|
246
|
+
total_chunks: number;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export interface StateRehydrationChunkEvent extends SidekickEventEnvelope {
|
|
250
|
+
kind: typeof SIDEKICK_EVENT_KINDS.STATE_REHYDRATION_CHUNK;
|
|
251
|
+
/** Correlation id matching the {@link StateRehydrationStartedEvent}. */
|
|
252
|
+
rehydration_id: string;
|
|
253
|
+
/**
|
|
254
|
+
* Monotonic zero-based index identifying this chunk's position within the
|
|
255
|
+
* stream. Receivers reassemble ordered by `cursor`.
|
|
256
|
+
*/
|
|
257
|
+
cursor: number;
|
|
258
|
+
/** True for the final chunk in the stream (cursor === total_chunks - 1). */
|
|
259
|
+
last_chunk: boolean;
|
|
260
|
+
/**
|
|
261
|
+
* Subset of {@link SidekickStores} keys carried by this chunk. Receivers
|
|
262
|
+
* merge `stores` into the accumulating snapshot keyed on store name.
|
|
263
|
+
*/
|
|
264
|
+
stores: Partial<SidekickStores>;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export interface StateRehydrationCompletedEvent extends SidekickEventEnvelope {
|
|
268
|
+
kind: typeof SIDEKICK_EVENT_KINDS.STATE_REHYDRATION_COMPLETED;
|
|
269
|
+
rehydration_id: string;
|
|
270
|
+
chunk_count: number;
|
|
271
|
+
}
|
|
272
|
+
|
|
207
273
|
export type SidekickEvent =
|
|
208
274
|
| TaskCreatedEvent
|
|
209
275
|
| TaskCompletedEvent
|
|
@@ -220,7 +286,10 @@ export type SidekickEvent =
|
|
|
220
286
|
| RoutineCommittedEvent
|
|
221
287
|
| RoutineExecutedEvent
|
|
222
288
|
| RoutineStepFailedEvent
|
|
223
|
-
| StateRehydratedEvent
|
|
289
|
+
| StateRehydratedEvent
|
|
290
|
+
| StateRehydrationStartedEvent
|
|
291
|
+
| StateRehydrationChunkEvent
|
|
292
|
+
| StateRehydrationCompletedEvent;
|
|
224
293
|
|
|
225
294
|
const channelSeqCounters = new Map<string, number>();
|
|
226
295
|
|
|
@@ -406,7 +475,7 @@ export function buildMemoryForgottenEvent(memory_id: string): MemoryForgottenEve
|
|
|
406
475
|
}
|
|
407
476
|
|
|
408
477
|
export function buildChannelMessageSentEvent(
|
|
409
|
-
message:
|
|
478
|
+
message: ChannelMessageRecord,
|
|
410
479
|
): ChannelMessageSentEvent {
|
|
411
480
|
return stampSidekickEvent<ChannelMessageSentEvent>({
|
|
412
481
|
kind: SIDEKICK_EVENT_KINDS.CHANNEL_MESSAGE_SENT,
|
|
@@ -416,13 +485,13 @@ export function buildChannelMessageSentEvent(
|
|
|
416
485
|
|
|
417
486
|
export function buildChannelMessageReceivedEvent(input: {
|
|
418
487
|
channel: string;
|
|
419
|
-
|
|
488
|
+
messages: ChannelMessageRecord[];
|
|
420
489
|
provider?: string;
|
|
421
490
|
}): ChannelMessageReceivedEvent {
|
|
422
491
|
return stampSidekickEvent<ChannelMessageReceivedEvent>({
|
|
423
492
|
kind: SIDEKICK_EVENT_KINDS.CHANNEL_MESSAGE_RECEIVED,
|
|
424
493
|
channel: input.channel,
|
|
425
|
-
|
|
494
|
+
messages: input.messages,
|
|
426
495
|
...(input.provider ? { provider: input.provider } : {}),
|
|
427
496
|
});
|
|
428
497
|
}
|
|
@@ -495,6 +564,11 @@ export function buildRoutineStepFailedEvent(input: {
|
|
|
495
564
|
});
|
|
496
565
|
}
|
|
497
566
|
|
|
567
|
+
/**
|
|
568
|
+
* @deprecated WU-2830 (INIT-062 WU-D): prefer
|
|
569
|
+
* {@link buildStateRehydrationChunkEvents} which emits the bounded
|
|
570
|
+
* started/chunk/completed trio. Retained for backwards compatibility.
|
|
571
|
+
*/
|
|
498
572
|
export function buildStateRehydratedEvent(snapshot: SidekickStores): StateRehydratedEvent {
|
|
499
573
|
return stampSidekickEvent<StateRehydratedEvent>({
|
|
500
574
|
kind: SIDEKICK_EVENT_KINDS.STATE_REHYDRATED,
|
|
@@ -502,16 +576,119 @@ export function buildStateRehydratedEvent(snapshot: SidekickStores): StateRehydr
|
|
|
502
576
|
});
|
|
503
577
|
}
|
|
504
578
|
|
|
505
|
-
export function
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
):
|
|
509
|
-
return {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
579
|
+
export function buildStateRehydrationStartedEvent(input: {
|
|
580
|
+
rehydration_id: string;
|
|
581
|
+
total_chunks: number;
|
|
582
|
+
}): StateRehydrationStartedEvent {
|
|
583
|
+
return stampSidekickEvent<StateRehydrationStartedEvent>({
|
|
584
|
+
kind: SIDEKICK_EVENT_KINDS.STATE_REHYDRATION_STARTED,
|
|
585
|
+
rehydration_id: input.rehydration_id,
|
|
586
|
+
total_chunks: input.total_chunks,
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
export function buildStateRehydrationChunkEvent(input: {
|
|
591
|
+
rehydration_id: string;
|
|
592
|
+
cursor: number;
|
|
593
|
+
last_chunk: boolean;
|
|
594
|
+
stores: Partial<SidekickStores>;
|
|
595
|
+
}): StateRehydrationChunkEvent {
|
|
596
|
+
return stampSidekickEvent<StateRehydrationChunkEvent>({
|
|
597
|
+
kind: SIDEKICK_EVENT_KINDS.STATE_REHYDRATION_CHUNK,
|
|
598
|
+
rehydration_id: input.rehydration_id,
|
|
599
|
+
cursor: input.cursor,
|
|
600
|
+
last_chunk: input.last_chunk,
|
|
601
|
+
stores: input.stores,
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
export function buildStateRehydrationCompletedEvent(input: {
|
|
606
|
+
rehydration_id: string;
|
|
607
|
+
chunk_count: number;
|
|
608
|
+
}): StateRehydrationCompletedEvent {
|
|
609
|
+
return stampSidekickEvent<StateRehydrationCompletedEvent>({
|
|
610
|
+
kind: SIDEKICK_EVENT_KINDS.STATE_REHYDRATION_COMPLETED,
|
|
611
|
+
rehydration_id: input.rehydration_id,
|
|
612
|
+
chunk_count: input.chunk_count,
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* WU-2830 (INIT-062 WU-D): the store order used by the chunked rehydration
|
|
618
|
+
* emitter. Exposed so receivers that care about deterministic reassembly
|
|
619
|
+
* can cross-check cursor semantics.
|
|
620
|
+
*/
|
|
621
|
+
export const SIDEKICK_REHYDRATION_STORE_ORDER = [
|
|
622
|
+
'tasks',
|
|
623
|
+
'memories',
|
|
624
|
+
'channels',
|
|
625
|
+
'messages',
|
|
626
|
+
'routines',
|
|
627
|
+
] as const satisfies readonly StoreName[];
|
|
628
|
+
|
|
629
|
+
export type SidekickRehydrationStore = (typeof SIDEKICK_REHYDRATION_STORE_ORDER)[number];
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* WU-2830 (INIT-062 WU-D): emit the chunked rehydration stream.
|
|
633
|
+
*
|
|
634
|
+
* Emission order: `state_rehydration_started` → one
|
|
635
|
+
* `state_rehydration_chunk` per store in
|
|
636
|
+
* {@link SIDEKICK_REHYDRATION_STORE_ORDER} → `state_rehydration_completed`.
|
|
637
|
+
* `cursor` is a zero-based monotonic index; `last_chunk` is true on the final
|
|
638
|
+
* chunk so streaming receivers can close the reassembly window without
|
|
639
|
+
* waiting for the completion event.
|
|
640
|
+
*/
|
|
641
|
+
export async function emitSidekickStateRehydration(snapshot: SidekickStores): Promise<{
|
|
642
|
+
rehydration_id: string;
|
|
643
|
+
chunk_count: number;
|
|
644
|
+
}> {
|
|
645
|
+
const rehydration_id = randomUUID();
|
|
646
|
+
const stores = SIDEKICK_REHYDRATION_STORE_ORDER;
|
|
647
|
+
const total_chunks = stores.length;
|
|
648
|
+
|
|
649
|
+
await emitSidekickEvent(buildStateRehydrationStartedEvent({ rehydration_id, total_chunks }));
|
|
650
|
+
|
|
651
|
+
for (let cursor = 0; cursor < stores.length; cursor++) {
|
|
652
|
+
const store = stores[cursor];
|
|
653
|
+
if (store === undefined) {
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
const last_chunk = cursor === stores.length - 1;
|
|
657
|
+
const chunkStores: Partial<SidekickStores> = {};
|
|
658
|
+
// Narrow the union so TS keeps the store-value type aligned with the key.
|
|
659
|
+
switch (store) {
|
|
660
|
+
case 'tasks':
|
|
661
|
+
chunkStores.tasks = snapshot.tasks;
|
|
662
|
+
break;
|
|
663
|
+
case 'memories':
|
|
664
|
+
chunkStores.memories = snapshot.memories;
|
|
665
|
+
break;
|
|
666
|
+
case 'channels':
|
|
667
|
+
chunkStores.channels = snapshot.channels;
|
|
668
|
+
break;
|
|
669
|
+
case 'messages':
|
|
670
|
+
chunkStores.messages = snapshot.messages;
|
|
671
|
+
break;
|
|
672
|
+
case 'routines':
|
|
673
|
+
chunkStores.routines = snapshot.routines;
|
|
674
|
+
break;
|
|
675
|
+
}
|
|
676
|
+
await emitSidekickEvent(
|
|
677
|
+
buildStateRehydrationChunkEvent({
|
|
678
|
+
rehydration_id,
|
|
679
|
+
cursor,
|
|
680
|
+
last_chunk,
|
|
681
|
+
stores: chunkStores,
|
|
682
|
+
}),
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
await emitSidekickEvent(
|
|
687
|
+
buildStateRehydrationCompletedEvent({
|
|
688
|
+
rehydration_id,
|
|
689
|
+
chunk_count: total_chunks,
|
|
690
|
+
}),
|
|
691
|
+
);
|
|
692
|
+
|
|
693
|
+
return { rehydration_id, chunk_count: total_chunks };
|
|
517
694
|
}
|
|
@@ -294,18 +294,26 @@ export function createControlPlaneChannelBridge(
|
|
|
294
294
|
// Port methods
|
|
295
295
|
// -------------------------------------------------------------------------
|
|
296
296
|
|
|
297
|
+
async function registerInternal(bridgeConfig: BridgeConfig): Promise<ChannelId> {
|
|
298
|
+
const key = `${bridgeConfig.provider}::${bridgeConfig.name}::${hashOptions(
|
|
299
|
+
bridgeConfig.options as Record<string, unknown> | undefined,
|
|
300
|
+
)}`;
|
|
301
|
+
const existing = registry.get(key);
|
|
302
|
+
if (existing) {
|
|
303
|
+
return existing;
|
|
304
|
+
}
|
|
305
|
+
const id = mintChannelId(bridgeConfig);
|
|
306
|
+
registry.set(key, id);
|
|
307
|
+
return id;
|
|
308
|
+
}
|
|
309
|
+
|
|
297
310
|
return {
|
|
311
|
+
async connect(bridgeConfig: BridgeConfig): Promise<ChannelId> {
|
|
312
|
+
return registerInternal(bridgeConfig);
|
|
313
|
+
},
|
|
314
|
+
|
|
298
315
|
async register(bridgeConfig: BridgeConfig): Promise<ChannelId> {
|
|
299
|
-
|
|
300
|
-
bridgeConfig.options as Record<string, unknown> | undefined,
|
|
301
|
-
)}`;
|
|
302
|
-
const existing = registry.get(key);
|
|
303
|
-
if (existing) {
|
|
304
|
-
return existing;
|
|
305
|
-
}
|
|
306
|
-
const id = mintChannelId(bridgeConfig);
|
|
307
|
-
registry.set(key, id);
|
|
308
|
-
return id;
|
|
316
|
+
return registerInternal(bridgeConfig);
|
|
309
317
|
},
|
|
310
318
|
|
|
311
319
|
async send(channelId: ChannelId, envelope: ChannelEnvelope): Promise<SendResult> {
|
|
@@ -180,6 +180,10 @@ export function createFilesystemChannelBridge(
|
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
return {
|
|
183
|
+
async connect(bridgeConfig: BridgeConfig): Promise<ChannelId> {
|
|
184
|
+
return ensureRegistered(bridgeConfig);
|
|
185
|
+
},
|
|
186
|
+
|
|
183
187
|
async register(bridgeConfig: BridgeConfig): Promise<ChannelId> {
|
|
184
188
|
return ensureRegistered(bridgeConfig);
|
|
185
189
|
},
|