@undefineds.co/linx 0.3.8 → 0.3.13
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 +17 -0
- package/dist/generated/version.js +2 -2
- package/dist/generated/version.js.map +1 -1
- package/dist/index.js +13 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/auto-mode/pod-approval.js +216 -2
- package/dist/lib/auto-mode/pod-approval.js.map +1 -1
- package/dist/lib/linx-status-line.js +335 -0
- package/dist/lib/linx-status-line.js.map +1 -0
- package/dist/lib/linx-tui-contract.js +3 -3
- package/dist/lib/linx-tui-contract.js.map +1 -1
- package/dist/lib/models.js +2 -2
- package/dist/lib/models.js.map +1 -1
- package/dist/lib/pi-adapter/auth.js +68 -0
- package/dist/lib/pi-adapter/auth.js.map +1 -0
- package/dist/lib/pi-adapter/interactive.js +326 -231
- package/dist/lib/pi-adapter/interactive.js.map +1 -1
- package/dist/lib/pi-adapter/pod-mirror.js +52 -2
- package/dist/lib/pi-adapter/pod-mirror.js.map +1 -1
- package/dist/lib/pi-adapter/pod-tools.js +140 -0
- package/dist/lib/pi-adapter/pod-tools.js.map +1 -0
- package/dist/lib/pi-adapter/runtime.js +14 -4
- package/dist/lib/pi-adapter/runtime.js.map +1 -1
- package/dist/lib/pi-adapter/stream.js +24 -1
- package/dist/lib/pi-adapter/stream.js.map +1 -1
- package/dist/lib/status-line-command.js +108 -0
- package/dist/lib/status-line-command.js.map +1 -0
- package/dist/lib/symphony/pod-projection.js +15 -7
- package/dist/lib/symphony/pod-projection.js.map +1 -1
- package/dist/lib/symphony-command.js +20 -21
- package/dist/lib/symphony-command.js.map +1 -1
- package/dist/skills/symphony/SKILL.md +69 -10
- package/dist/skills/xpod-cli/SKILL.md +70 -0
- package/package.json +9 -3
- package/vendor/agent-runtime/dist/client-inbox-subscription.d.ts +56 -0
- package/vendor/agent-runtime/dist/client-inbox-subscription.js +93 -0
- package/vendor/agent-runtime/dist/index.d.ts +1 -0
- package/vendor/agent-runtime/dist/index.js +1 -0
- package/vendor/agent-runtime/dist/reconciler.d.ts +60 -1
- package/vendor/agent-runtime/dist/reconciler.js +148 -4
- package/vendor/agent-runtime/dist/thread-reconciler-controller.d.ts +2 -1
- package/vendor/agent-runtime/dist/thread-reconciler-controller.js +4 -0
|
@@ -2,6 +2,7 @@ export * from './acp.js';
|
|
|
2
2
|
export * from './agent-runtime.js';
|
|
3
3
|
export * from './auto-mode.js';
|
|
4
4
|
export * from './companion-model.js';
|
|
5
|
+
export * from './client-inbox-subscription.js';
|
|
5
6
|
export * from './control-plane.js';
|
|
6
7
|
export * from './file-sync.js';
|
|
7
8
|
export * from './reconciler.js';
|
|
@@ -2,6 +2,7 @@ export * from './acp.js';
|
|
|
2
2
|
export * from './agent-runtime.js';
|
|
3
3
|
export * from './auto-mode.js';
|
|
4
4
|
export * from './companion-model.js';
|
|
5
|
+
export * from './client-inbox-subscription.js';
|
|
5
6
|
export * from './control-plane.js';
|
|
6
7
|
export * from './file-sync.js';
|
|
7
8
|
export * from './reconciler.js';
|
|
@@ -1,11 +1,43 @@
|
|
|
1
1
|
import type { AgentParticipantRole } from './turn-controller.js';
|
|
2
2
|
export type ThreadPolicyKind = 'direct' | 'auto' | 'symphony' | 'open_group' | 'review';
|
|
3
3
|
export type ThreadKind = 'main' | 'control' | 'worker' | 'review' | 'schedule' | 'schedule_run';
|
|
4
|
-
export type ReconcilerEventType = 'message.appended' | 'input.required' | 'approval.required' | 'delivery.submitted' | 'delivery.completed' | 'delivery.failed' | 'schedule.tick' | 'worker.blocked' | 'change.requested' | 'issue.updated' | 'task.updated' | 'run.updated' | (string & {});
|
|
4
|
+
export type ReconcilerEventType = 'message.appended' | 'input.required' | 'approval.required' | 'inbox.notification.created' | 'inbox.notification.updated' | 'delivery.submitted' | 'delivery.completed' | 'delivery.failed' | 'schedule.tick' | 'worker.blocked' | 'change.requested' | 'issue.updated' | 'task.updated' | 'run.updated' | (string & {});
|
|
5
5
|
export type ReconcilerActorRole = AgentParticipantRole | 'worker' | 'reviewer' | 'runtime' | 'scheduler' | 'tool' | 'assistant';
|
|
6
6
|
export type WakeJobTargetRole = AgentParticipantRole | 'worker' | 'reviewer';
|
|
7
7
|
export type WakeJobPriority = 'low' | 'normal' | 'high';
|
|
8
8
|
export type WakeJobStatus = 'queued';
|
|
9
|
+
export type ReconcilerNotificationAudience = 'user';
|
|
10
|
+
export type ReconcilerNotificationChannel = 'inbox';
|
|
11
|
+
export type ReconcilerClientFocusState = 'focused' | 'background' | 'closed';
|
|
12
|
+
export type ReconcilerControlResourceClaimStatus = 'claimed' | 'lost' | 'display_only' | 'none';
|
|
13
|
+
export interface ReconcilerControlResourceClaimState {
|
|
14
|
+
status: ReconcilerControlResourceClaimStatus;
|
|
15
|
+
controlResource?: string;
|
|
16
|
+
leaseOwner?: string;
|
|
17
|
+
leaseExpiresAt?: string;
|
|
18
|
+
reason?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface ReconcilerClientContext {
|
|
21
|
+
id: string;
|
|
22
|
+
agentCapable?: boolean;
|
|
23
|
+
secretaryRuntimeAvailable?: boolean;
|
|
24
|
+
focusState?: ReconcilerClientFocusState;
|
|
25
|
+
activeThread?: string;
|
|
26
|
+
activeChat?: string;
|
|
27
|
+
generationLocked?: boolean;
|
|
28
|
+
controlResourceClaim?: ReconcilerControlResourceClaimState;
|
|
29
|
+
}
|
|
30
|
+
export interface SecretaryInboxWakeContext {
|
|
31
|
+
eventId: string;
|
|
32
|
+
eventType: ReconcilerEventType;
|
|
33
|
+
controlResource?: string;
|
|
34
|
+
sourceThread?: string;
|
|
35
|
+
sourceRun?: string;
|
|
36
|
+
sourceTask?: string;
|
|
37
|
+
requestKind?: string;
|
|
38
|
+
priority: WakeJobPriority;
|
|
39
|
+
shortSummary?: string;
|
|
40
|
+
}
|
|
9
41
|
export interface ReconcilerActorRef {
|
|
10
42
|
id?: string;
|
|
11
43
|
role?: ReconcilerActorRole;
|
|
@@ -60,12 +92,32 @@ export interface WakeJob {
|
|
|
60
92
|
sourceEventType: ReconcilerEventType;
|
|
61
93
|
createdAt: string;
|
|
62
94
|
}
|
|
95
|
+
export interface ReconcilerNotificationEvent {
|
|
96
|
+
id: string;
|
|
97
|
+
thread: string;
|
|
98
|
+
chat?: string;
|
|
99
|
+
audience: ReconcilerNotificationAudience;
|
|
100
|
+
channel: ReconcilerNotificationChannel;
|
|
101
|
+
priority: WakeJobPriority;
|
|
102
|
+
reason: string;
|
|
103
|
+
sourceEventId?: string;
|
|
104
|
+
sourceEventType: ReconcilerEventType;
|
|
105
|
+
sourceResource?: string;
|
|
106
|
+
inboxNotification?: string;
|
|
107
|
+
sourceThread?: string;
|
|
108
|
+
sourceRun?: string;
|
|
109
|
+
sourceTask?: string;
|
|
110
|
+
requestKind?: string;
|
|
111
|
+
shortSummary?: string;
|
|
112
|
+
createdAt: string;
|
|
113
|
+
}
|
|
63
114
|
export interface ReconcileDecision {
|
|
64
115
|
id: string;
|
|
65
116
|
policyKind: ThreadPolicyKind;
|
|
66
117
|
event: ThreadControlEvent;
|
|
67
118
|
placement: ThreadPlacement;
|
|
68
119
|
wakeJobs: WakeJob[];
|
|
120
|
+
notificationEvents?: ReconcilerNotificationEvent[];
|
|
69
121
|
skippedReason?: string;
|
|
70
122
|
createdAt: string;
|
|
71
123
|
}
|
|
@@ -82,6 +134,7 @@ export interface WakeJobSummary {
|
|
|
82
134
|
sourceEventId?: string;
|
|
83
135
|
sourceEventType: ReconcilerEventType;
|
|
84
136
|
sourceResource?: string;
|
|
137
|
+
inboxNotification?: string;
|
|
85
138
|
controlGate?: string;
|
|
86
139
|
}
|
|
87
140
|
export interface ReconcileDecisionSummary {
|
|
@@ -92,6 +145,7 @@ export interface ReconcileDecisionSummary {
|
|
|
92
145
|
chat?: string;
|
|
93
146
|
skippedReason?: string;
|
|
94
147
|
wakeJobs: WakeJobSummary[];
|
|
148
|
+
notificationEvents?: ReconcilerNotificationEvent[];
|
|
95
149
|
createdAt: string;
|
|
96
150
|
}
|
|
97
151
|
export interface ReconcileThreadEventInput {
|
|
@@ -101,6 +155,7 @@ export interface ReconcileThreadEventInput {
|
|
|
101
155
|
thread?: string;
|
|
102
156
|
now?: Date;
|
|
103
157
|
randomId?: string;
|
|
158
|
+
client?: ReconcilerClientContext;
|
|
104
159
|
}
|
|
105
160
|
export interface ThreadReconciler {
|
|
106
161
|
readonly policy: ThreadPolicy;
|
|
@@ -115,3 +170,7 @@ export declare function resolveThreadPlacement(input: {
|
|
|
115
170
|
thread?: string;
|
|
116
171
|
randomId?: string;
|
|
117
172
|
}): ThreadPlacement;
|
|
173
|
+
export declare function buildSecretaryInboxWakeContext(event: ThreadControlEvent, options?: {
|
|
174
|
+
priority?: WakeJobPriority;
|
|
175
|
+
}): SecretaryInboxWakeContext;
|
|
176
|
+
export declare function canClientScheduleInboxWake(client: ReconcilerClientContext | undefined, event: ThreadControlEvent, now?: Date | string | number): boolean;
|
|
@@ -24,14 +24,16 @@ export function reconcileThreadEvent(input) {
|
|
|
24
24
|
thread: input.thread,
|
|
25
25
|
randomId: input.randomId,
|
|
26
26
|
});
|
|
27
|
-
const jobs = selectWakeJobs(policy, event, placement, createdAt, input.randomId);
|
|
28
|
-
const
|
|
27
|
+
const jobs = selectWakeJobs(policy, event, placement, createdAt, input.randomId, input.client);
|
|
28
|
+
const notificationEvents = selectNotificationEvents(event, placement, createdAt, input.randomId);
|
|
29
|
+
const skippedReason = jobs.length === 0 ? skipReasonFor(policy, event, input.client) : undefined;
|
|
29
30
|
return {
|
|
30
31
|
id: createReconcilerId('decision', input.randomId),
|
|
31
32
|
policyKind: policy.kind,
|
|
32
33
|
event,
|
|
33
34
|
placement,
|
|
34
35
|
wakeJobs: jobs,
|
|
36
|
+
...(notificationEvents.length > 0 ? { notificationEvents } : {}),
|
|
35
37
|
...(skippedReason ? { skippedReason } : {}),
|
|
36
38
|
createdAt,
|
|
37
39
|
};
|
|
@@ -57,8 +59,12 @@ export function summarizeReconcileDecision(decision) {
|
|
|
57
59
|
...(job.sourceEventId ? { sourceEventId: job.sourceEventId } : {}),
|
|
58
60
|
sourceEventType: job.sourceEventType,
|
|
59
61
|
...(decision.event.resource ? { sourceResource: decision.event.resource } : {}),
|
|
62
|
+
...(normalizeText(decision.event.data?.inboxNotification) ? { inboxNotification: normalizeText(decision.event.data?.inboxNotification) } : {}),
|
|
60
63
|
...(resolveControlGate(decision.event) ? { controlGate: resolveControlGate(decision.event) } : {}),
|
|
61
64
|
})),
|
|
65
|
+
...(decision.notificationEvents && decision.notificationEvents.length > 0
|
|
66
|
+
? { notificationEvents: decision.notificationEvents.map((event) => ({ ...event })) }
|
|
67
|
+
: {}),
|
|
62
68
|
createdAt: decision.createdAt,
|
|
63
69
|
};
|
|
64
70
|
}
|
|
@@ -114,7 +120,7 @@ export function resolveThreadPlacement(input) {
|
|
|
114
120
|
kind: 'control',
|
|
115
121
|
};
|
|
116
122
|
}
|
|
117
|
-
function selectWakeJobs(policy, event, placement, createdAt, randomId) {
|
|
123
|
+
function selectWakeJobs(policy, event, placement, createdAt, randomId, client) {
|
|
118
124
|
if (policy.kind === 'direct') {
|
|
119
125
|
return isUserMessage(event)
|
|
120
126
|
? [createWakeJob(policy, event, placement, {
|
|
@@ -128,6 +134,11 @@ function selectWakeJobs(policy, event, placement, createdAt, randomId) {
|
|
|
128
134
|
: [];
|
|
129
135
|
}
|
|
130
136
|
if (policy.kind === 'auto') {
|
|
137
|
+
if (isActionableInboxEvent(event)) {
|
|
138
|
+
return canClientScheduleInboxWake(client, event, createdAt)
|
|
139
|
+
? [createSecretaryWakeJob(policy, event, placement, 'Auto mode claimed an actionable control resource on this client and schedules the same-Thread Secretary to check Inbox.', 'high', createdAt, randomId)]
|
|
140
|
+
: [];
|
|
141
|
+
}
|
|
131
142
|
if (isInputApprovalOrBlocker(event)) {
|
|
132
143
|
return [createSecretaryWakeJob(policy, event, placement, 'Auto mode routes input, approval, and blocker events to the same-Thread Secretary.', 'high', createdAt, randomId)];
|
|
133
144
|
}
|
|
@@ -140,6 +151,11 @@ function selectWakeJobs(policy, event, placement, createdAt, randomId) {
|
|
|
140
151
|
return [];
|
|
141
152
|
}
|
|
142
153
|
if (policy.kind === 'symphony') {
|
|
154
|
+
if (isActionableInboxEvent(event)) {
|
|
155
|
+
return canClientScheduleInboxWake(client, event, createdAt)
|
|
156
|
+
? [createSecretaryWakeJob(policy, event, placement, 'Symphony claimed an actionable control resource on this client and schedules Secretary to check Inbox; the payload is runtime control context, not a human user message.', 'high', createdAt, randomId)]
|
|
157
|
+
: [];
|
|
158
|
+
}
|
|
143
159
|
if (isInputApprovalOrBlocker(event) || event.type === 'change.requested') {
|
|
144
160
|
return [createSecretaryWakeJob(policy, event, placement, 'Symphony routes blocker, input, approval, and change requests to Secretary for semantic judgment.', 'high', createdAt, randomId)];
|
|
145
161
|
}
|
|
@@ -197,6 +213,92 @@ function selectWakeJobs(policy, event, placement, createdAt, randomId) {
|
|
|
197
213
|
}
|
|
198
214
|
return [];
|
|
199
215
|
}
|
|
216
|
+
function selectNotificationEvents(event, placement, createdAt, randomId) {
|
|
217
|
+
if (!isActionableInboxEvent(event)) {
|
|
218
|
+
return [];
|
|
219
|
+
}
|
|
220
|
+
const wakeContext = buildSecretaryInboxWakeContext(event, { priority: 'high' });
|
|
221
|
+
return [{
|
|
222
|
+
id: createReconcilerId('notice', randomId ?? event.id),
|
|
223
|
+
thread: placement.thread,
|
|
224
|
+
...(placement.chat ? { chat: placement.chat } : {}),
|
|
225
|
+
audience: 'user',
|
|
226
|
+
channel: 'inbox',
|
|
227
|
+
priority: 'high',
|
|
228
|
+
reason: 'Inbox envelope changed; notify subscribed clients to refresh Inbox and read the linked control resource without converting it into chat.',
|
|
229
|
+
...(event.id ? { sourceEventId: event.id } : {}),
|
|
230
|
+
sourceEventType: event.type,
|
|
231
|
+
...(event.resource ? { sourceResource: event.resource } : {}),
|
|
232
|
+
...(normalizeText(event.data?.inboxNotification) ? { inboxNotification: normalizeText(event.data?.inboxNotification) } : {}),
|
|
233
|
+
...(wakeContext.sourceThread ? { sourceThread: wakeContext.sourceThread } : {}),
|
|
234
|
+
...(wakeContext.sourceRun ? { sourceRun: wakeContext.sourceRun } : {}),
|
|
235
|
+
...(wakeContext.sourceTask ? { sourceTask: wakeContext.sourceTask } : {}),
|
|
236
|
+
...(wakeContext.requestKind ? { requestKind: wakeContext.requestKind } : {}),
|
|
237
|
+
...(wakeContext.shortSummary ? { shortSummary: wakeContext.shortSummary } : {}),
|
|
238
|
+
createdAt,
|
|
239
|
+
}];
|
|
240
|
+
}
|
|
241
|
+
export function buildSecretaryInboxWakeContext(event, options = {}) {
|
|
242
|
+
const controlResource = normalizeText(event.resource)
|
|
243
|
+
?? normalizeText(event.data?.controlResource)
|
|
244
|
+
?? normalizeText(event.data?.controlResourceId)
|
|
245
|
+
?? normalizeText(event.data?.approval)
|
|
246
|
+
?? normalizeText(event.data?.inputRequest);
|
|
247
|
+
const requestKind = normalizeText(event.data?.requestKind)
|
|
248
|
+
?? normalizeText(event.data?.kind)
|
|
249
|
+
?? normalizeText(event.data?.type);
|
|
250
|
+
const sourceThread = normalizeText(event.data?.sourceThread);
|
|
251
|
+
const sourceRun = normalizeText(event.data?.sourceRun);
|
|
252
|
+
const sourceTask = normalizeText(event.data?.sourceTask);
|
|
253
|
+
const shortSummary = normalizeText(event.data?.shortSummary)
|
|
254
|
+
?? normalizeText(event.data?.summary)
|
|
255
|
+
?? normalizeText(event.content);
|
|
256
|
+
return {
|
|
257
|
+
eventId: event.id ?? createReconcilerId('event', controlResource ?? requestKind),
|
|
258
|
+
eventType: event.type,
|
|
259
|
+
...(controlResource ? { controlResource } : {}),
|
|
260
|
+
...(sourceThread ? { sourceThread } : {}),
|
|
261
|
+
...(sourceRun ? { sourceRun } : {}),
|
|
262
|
+
...(sourceTask ? { sourceTask } : {}),
|
|
263
|
+
...(requestKind ? { requestKind } : {}),
|
|
264
|
+
priority: options.priority ?? 'normal',
|
|
265
|
+
...(shortSummary ? { shortSummary } : {}),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
export function canClientScheduleInboxWake(client, event, now = Date.now()) {
|
|
269
|
+
if (!client || !isActionableInboxEvent(event)) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
if (client.agentCapable !== true) {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
if (client.secretaryRuntimeAvailable === false) {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
const claim = client.controlResourceClaim;
|
|
279
|
+
if (!claim || claim.status !== 'claimed') {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
const eventControlResource = normalizeText(event.resource)
|
|
283
|
+
?? normalizeText(event.data?.controlResource)
|
|
284
|
+
?? normalizeText(event.data?.controlResourceId);
|
|
285
|
+
const claimControlResource = normalizeText(claim.controlResource);
|
|
286
|
+
if (eventControlResource && claimControlResource && eventControlResource !== claimControlResource) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
const leaseOwner = normalizeText(claim.leaseOwner);
|
|
290
|
+
if (leaseOwner && leaseOwner !== client.id) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
if (claim.leaseExpiresAt) {
|
|
294
|
+
const expiresAt = Date.parse(claim.leaseExpiresAt);
|
|
295
|
+
const nowMs = typeof now === 'number' ? now : Date.parse(now instanceof Date ? now.toISOString() : now);
|
|
296
|
+
if (!Number.isNaN(expiresAt) && !Number.isNaN(nowMs) && expiresAt <= nowMs) {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
200
302
|
function createSecretaryWakeJob(policy, event, placement, reason, priority, createdAt, randomId) {
|
|
201
303
|
return createWakeJob(policy, event, placement, {
|
|
202
304
|
targetAgent: policy.secretaryAgent ?? DEFAULT_SECRETARY_AGENT,
|
|
@@ -245,12 +347,34 @@ function threadKindFromEvent(event) {
|
|
|
245
347
|
if (event.type === 'issue.updated' || event.type === 'task.updated' || event.type === 'run.updated') {
|
|
246
348
|
return 'control';
|
|
247
349
|
}
|
|
350
|
+
if (isInboxEvent(event)) {
|
|
351
|
+
return 'control';
|
|
352
|
+
}
|
|
248
353
|
return 'main';
|
|
249
354
|
}
|
|
250
355
|
function isInputApprovalOrBlocker(event) {
|
|
251
356
|
return event.type === 'input.required' || event.type === 'approval.required' || event.type === 'worker.blocked';
|
|
252
357
|
}
|
|
253
358
|
function resolveControlGate(event) {
|
|
359
|
+
if (isInboxEvent(event)) {
|
|
360
|
+
const explicitGate = normalizeText(event.data?.controlGate);
|
|
361
|
+
if (explicitGate) {
|
|
362
|
+
return explicitGate;
|
|
363
|
+
}
|
|
364
|
+
const requestKind = normalizeText(event.data?.requestKind)
|
|
365
|
+
?? normalizeText(event.data?.kind)
|
|
366
|
+
?? normalizeText(event.data?.type);
|
|
367
|
+
if (requestKind && /approval|auth|credential|grant|permission|destructive/iu.test(requestKind)) {
|
|
368
|
+
return 'authority';
|
|
369
|
+
}
|
|
370
|
+
if (requestKind && /block|fail|feasibil/iu.test(requestKind)) {
|
|
371
|
+
return 'feasibility';
|
|
372
|
+
}
|
|
373
|
+
if (requestKind && /change|scope|rebase|steer/iu.test(requestKind)) {
|
|
374
|
+
return 'change';
|
|
375
|
+
}
|
|
376
|
+
return 'binding';
|
|
377
|
+
}
|
|
254
378
|
if (event.type === 'input.required' || event.type === 'approval.required') {
|
|
255
379
|
return 'authority';
|
|
256
380
|
}
|
|
@@ -290,6 +414,19 @@ function isPrimaryAgentMessage(event) {
|
|
|
290
414
|
|| event.data?.role === 'assistant'
|
|
291
415
|
|| event.data?.source === 'primary-agent');
|
|
292
416
|
}
|
|
417
|
+
function isInboxEvent(event) {
|
|
418
|
+
return event.type === 'inbox.notification.created' || event.type === 'inbox.notification.updated';
|
|
419
|
+
}
|
|
420
|
+
function isActionableInboxEvent(event) {
|
|
421
|
+
if (!isInboxEvent(event)) {
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
const status = normalizeText(event.data?.status);
|
|
425
|
+
if (!status) {
|
|
426
|
+
return event.type === 'inbox.notification.created';
|
|
427
|
+
}
|
|
428
|
+
return status === 'pending' || status === 'handling';
|
|
429
|
+
}
|
|
293
430
|
function isTaskDispatchDelivery(event) {
|
|
294
431
|
return event.data?.deliveryType === 'task_dispatch'
|
|
295
432
|
|| event.data?.type === 'task_dispatch'
|
|
@@ -328,10 +465,17 @@ function isMentioned(content, agent) {
|
|
|
328
465
|
.filter(Boolean)
|
|
329
466
|
.some((name) => normalized.includes(`@${name.toLowerCase()}`));
|
|
330
467
|
}
|
|
331
|
-
function skipReasonFor(policy, event) {
|
|
468
|
+
function skipReasonFor(policy, event, client) {
|
|
332
469
|
if (policy.kind === 'open_group') {
|
|
333
470
|
return 'No mentioned or subscribed agents matched the event.';
|
|
334
471
|
}
|
|
472
|
+
if (isActionableInboxEvent(event) && (policy.kind === 'auto' || policy.kind === 'symphony')) {
|
|
473
|
+
if (!client) {
|
|
474
|
+
return `Policy ${policy.kind} keeps ${event.type} display-only until a subscribed client claims the control resource.`;
|
|
475
|
+
}
|
|
476
|
+
const claimStatus = client.controlResourceClaim?.status ?? 'none';
|
|
477
|
+
return `Policy ${policy.kind} keeps ${event.type} display-only for client ${client.id} because control resource claim status is ${claimStatus}.`;
|
|
478
|
+
}
|
|
335
479
|
return `Policy ${policy.kind} does not wake an agent for ${event.type}.`;
|
|
336
480
|
}
|
|
337
481
|
function createSyntheticThreadUri(kind, value) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ReconcileDecision, type ReconcileDecisionSummary, type ReconcileThreadEventInput, type ThreadControlEvent, type ThreadPolicy, type ThreadPolicyKind, type WakeJob } from './reconciler.js';
|
|
1
|
+
import { type ReconcileDecision, type ReconcileDecisionSummary, type ReconcileThreadEventInput, type ReconcilerNotificationEvent, type ThreadControlEvent, type ThreadPolicy, type ThreadPolicyKind, type WakeJob } from './reconciler.js';
|
|
2
2
|
import { type WakeJobExecutionRecord, type WakeJobExecutionRecordSummary, type WakeJobSchedulerSnapshot, type WakeJobSchedulerSnapshotSummary } from './wake-scheduler.js';
|
|
3
3
|
type MaybePromise<T> = T | Promise<T>;
|
|
4
4
|
export interface ThreadWakeJobContext {
|
|
@@ -19,6 +19,7 @@ export interface ThreadReconcilerControllerOptions {
|
|
|
19
19
|
onWakeJobStarted?: (record: WakeJobExecutionRecord, decision: ReconcileDecisionSummary) => void;
|
|
20
20
|
onWakeJobCompleted?: (record: WakeJobExecutionRecord, decision: ReconcileDecisionSummary) => void;
|
|
21
21
|
onWakeJobFailed?: (record: WakeJobExecutionRecord, decision: ReconcileDecisionSummary) => void;
|
|
22
|
+
onNotificationEvent?: (event: ReconcilerNotificationEvent, decision: ReconcileDecisionSummary) => void;
|
|
22
23
|
}
|
|
23
24
|
export interface ThreadReconcilerDispatchOptions extends Omit<ReconcileThreadEventInput, 'policy' | 'event'> {
|
|
24
25
|
}
|
|
@@ -58,10 +58,14 @@ class ConfiguredThreadReconcilerController {
|
|
|
58
58
|
dispatch(event, options = {}) {
|
|
59
59
|
const { decision, summary } = decideThreadControlEvent({
|
|
60
60
|
...options,
|
|
61
|
+
now: options.now ?? this.options.now?.(),
|
|
61
62
|
policy: this.options.policy,
|
|
62
63
|
event,
|
|
63
64
|
});
|
|
64
65
|
this.options.onDecision?.(summary);
|
|
66
|
+
for (const notificationEvent of decision.notificationEvents ?? []) {
|
|
67
|
+
this.options.onNotificationEvent?.(notificationEvent, summary);
|
|
68
|
+
}
|
|
65
69
|
for (const job of decision.wakeJobs) {
|
|
66
70
|
const key = defaultWakeJobDedupeKey(job);
|
|
67
71
|
if (!this.contexts.has(key)) {
|