@kammcs/buildit-mcp 0.10.0 → 0.12.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/CHANGELOG.md +47 -0
- package/README.md +18 -5
- package/dist/api/generated/operations.js +11 -2
- package/dist/api/generated/schemas.js +146 -48
- package/dist/api/generated/strict.js +160 -48
- package/dist/channel/loop.js +70 -0
- package/dist/channel/requests.js +297 -0
- package/dist/channel/threads.js +11 -0
- package/dist/channel/workers.js +14 -2
- package/dist/errors.js +4 -0
- package/dist/server.js +15 -2
- package/dist/tools/approve.js +2 -1
- package/dist/tools/ask.js +18 -3
- package/dist/tools/chat.js +92 -6
- package/dist/tools/comments.js +41 -8
- package/dist/tools/reply.js +71 -5
- package/dist/tools/shared.js +13 -0
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Exact schemas for the agent API: closed objects and enums, patterns and bounds. The test fake uses them to check requests and its own responses against the contract; the server parses responses with the tolerant schemas.ts.
|
|
3
3
|
*
|
|
4
4
|
* GENERATED by scripts/generate.ts from openapi/openapi.json (agent API
|
|
5
|
-
* 1.
|
|
5
|
+
* 1.14.0). Do not edit: change the contract, then run
|
|
6
6
|
* `npm run generate`.
|
|
7
7
|
*/
|
|
8
8
|
import { z } from 'zod';
|
|
@@ -94,6 +94,7 @@ export const EntityKindSchema = z.enum([
|
|
|
94
94
|
'approval',
|
|
95
95
|
'handoff',
|
|
96
96
|
'upload',
|
|
97
|
+
'request',
|
|
97
98
|
]);
|
|
98
99
|
export const EstimateSchema = z.number().min(0).max(9999.99);
|
|
99
100
|
export const EstimateScaleSchema = z.enum(['none', 'points', 'tshirt']);
|
|
@@ -1065,6 +1066,36 @@ export const ErrorSchema = z.strictObject({
|
|
|
1065
1066
|
.regex(new RegExp('^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$', 'u')),
|
|
1066
1067
|
}),
|
|
1067
1068
|
}),
|
|
1069
|
+
z.strictObject({
|
|
1070
|
+
code: z.literal('request_not_found'),
|
|
1071
|
+
message: z.string().min(1),
|
|
1072
|
+
hint: z.string().optional(),
|
|
1073
|
+
details: z.strictObject({
|
|
1074
|
+
request_id: z
|
|
1075
|
+
.string()
|
|
1076
|
+
.regex(new RegExp('^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$', 'u')),
|
|
1077
|
+
}),
|
|
1078
|
+
}),
|
|
1079
|
+
z.strictObject({
|
|
1080
|
+
code: z.literal('request_closed'),
|
|
1081
|
+
message: z.string().min(1),
|
|
1082
|
+
hint: z.string().optional(),
|
|
1083
|
+
details: z.strictObject({
|
|
1084
|
+
request_id: z
|
|
1085
|
+
.string()
|
|
1086
|
+
.regex(new RegExp('^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$', 'u')),
|
|
1087
|
+
}),
|
|
1088
|
+
}),
|
|
1089
|
+
z.strictObject({
|
|
1090
|
+
code: z.literal('request_result_private'),
|
|
1091
|
+
message: z.string().min(1),
|
|
1092
|
+
hint: z.string().optional(),
|
|
1093
|
+
details: z.strictObject({
|
|
1094
|
+
request_id: z
|
|
1095
|
+
.string()
|
|
1096
|
+
.regex(new RegExp('^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$', 'u')),
|
|
1097
|
+
}),
|
|
1098
|
+
}),
|
|
1068
1099
|
]),
|
|
1069
1100
|
});
|
|
1070
1101
|
export const FeedbackSchema = z.strictObject({
|
|
@@ -1204,6 +1235,7 @@ export const PostSessionMessageRequestSchema = z.strictObject({
|
|
|
1204
1235
|
conversation_id: UuidSchema.optional(),
|
|
1205
1236
|
card: QuestionCardSchema.optional(),
|
|
1206
1237
|
files: z.array(UuidSchema).min(1).max(10).optional(),
|
|
1238
|
+
request: UuidSchema.optional(),
|
|
1207
1239
|
});
|
|
1208
1240
|
export const PresignedPutSchema = z.strictObject({
|
|
1209
1241
|
url: z.string(),
|
|
@@ -1372,6 +1404,17 @@ export const SessionLinkFileSchema = z.strictObject({
|
|
|
1372
1404
|
duration_ms: z.int().min(1).optional(),
|
|
1373
1405
|
});
|
|
1374
1406
|
export const SessionOwnerSchema = z.strictObject({ id: UuidSchema, name: z.string() });
|
|
1407
|
+
export const SessionRequestChannelContextSchema = z.strictObject({
|
|
1408
|
+
type: z.literal('channel'),
|
|
1409
|
+
channel: z.strictObject({ id: UuidSchema, name: z.string().nullable() }),
|
|
1410
|
+
from: TimestampSchema,
|
|
1411
|
+
to: TimestampSchema,
|
|
1412
|
+
});
|
|
1413
|
+
export const SessionRequestThreadContextSchema = z.strictObject({
|
|
1414
|
+
type: z.literal('thread'),
|
|
1415
|
+
channel: z.strictObject({ id: UuidSchema, name: z.string().nullable(), dm: z.boolean() }),
|
|
1416
|
+
thread_id: UuidSchema,
|
|
1417
|
+
});
|
|
1375
1418
|
export const SessionRuleSchema = z.union([
|
|
1376
1419
|
z.strictObject({
|
|
1377
1420
|
type: z.literal('addRules'),
|
|
@@ -1776,39 +1819,11 @@ export const MeResponseSchema = z.strictObject({
|
|
|
1776
1819
|
projects: z.array(ProjectBriefSchema),
|
|
1777
1820
|
rate_limits: RateLimitsSchema,
|
|
1778
1821
|
});
|
|
1779
|
-
export const
|
|
1822
|
+
export const MessageItemSchema = z.strictObject({
|
|
1780
1823
|
id: UuidSchema,
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
body: z.string(),
|
|
1785
|
-
code: z.string().nullable(),
|
|
1786
|
-
code_language: z.string().nullable(),
|
|
1787
|
-
file: z
|
|
1788
|
-
.strictObject({
|
|
1789
|
-
id: UuidSchema,
|
|
1790
|
-
name: z.string(),
|
|
1791
|
-
type: z.string(),
|
|
1792
|
-
duration_ms: z.int().min(1).nullable(),
|
|
1793
|
-
})
|
|
1794
|
-
.nullable(),
|
|
1795
|
-
photos: z.array(z.strictObject({
|
|
1796
|
-
id: UuidSchema,
|
|
1797
|
-
name: z.string(),
|
|
1798
|
-
type: z.string(),
|
|
1799
|
-
alt: z.string().nullable(),
|
|
1800
|
-
duration_ms: z.int().min(1).nullable(),
|
|
1801
|
-
})),
|
|
1802
|
-
mentions: z.array(UserSchema),
|
|
1803
|
-
reply_count: z.int(),
|
|
1804
|
-
last_reply_at: TimestampSchema.nullable(),
|
|
1805
|
-
created_at: TimestampSchema,
|
|
1806
|
-
edited_at: TimestampSchema.nullable(),
|
|
1807
|
-
deleted: z.boolean(),
|
|
1808
|
-
via_agent: z.string().nullable(),
|
|
1809
|
-
pinned: z.boolean(),
|
|
1810
|
-
pinned_at: TimestampSchema.nullable(),
|
|
1811
|
-
pinned_by: MessageAuthorSchema.nullable(),
|
|
1824
|
+
key: ItemKeySchema,
|
|
1825
|
+
title: z.string(),
|
|
1826
|
+
status: StatusBriefSchema,
|
|
1812
1827
|
});
|
|
1813
1828
|
export const PageSchema = z.strictObject({
|
|
1814
1829
|
id: UuidSchema,
|
|
@@ -2012,6 +2027,21 @@ export const SessionMessageSchema = z.strictObject({
|
|
|
2012
2027
|
created_at: TimestampSchema,
|
|
2013
2028
|
session: SessionBriefSchema,
|
|
2014
2029
|
});
|
|
2030
|
+
export const SessionRequestBriefContextSchema = z.strictObject({
|
|
2031
|
+
type: z.literal('brief'),
|
|
2032
|
+
project: SessionEventProjectSchema,
|
|
2033
|
+
channel: z.strictObject({ id: UuidSchema, name: z.string().nullable() }),
|
|
2034
|
+
from: TimestampSchema,
|
|
2035
|
+
to: TimestampSchema,
|
|
2036
|
+
});
|
|
2037
|
+
export const SessionRequestItemContextSchema = z.strictObject({
|
|
2038
|
+
type: z.literal('item'),
|
|
2039
|
+
item: ItemIdKeySchema,
|
|
2040
|
+
});
|
|
2041
|
+
export const SessionRequestItemsContextSchema = z.strictObject({
|
|
2042
|
+
type: z.literal('items'),
|
|
2043
|
+
items: z.array(ItemIdKeySchema).min(1).max(20),
|
|
2044
|
+
});
|
|
2015
2045
|
export const SessionResponseSchema = z.strictObject({
|
|
2016
2046
|
session: z.strictObject({
|
|
2017
2047
|
id: UuidSchema,
|
|
@@ -2235,10 +2265,6 @@ export const LinkSchema = z.strictObject({
|
|
|
2235
2265
|
kind: LinkKindSchema,
|
|
2236
2266
|
item: LinkedItemSchema.nullable(),
|
|
2237
2267
|
});
|
|
2238
|
-
export const ListChannelMessagesResponseSchema = z.strictObject({
|
|
2239
|
-
items: z.array(MessageSchema),
|
|
2240
|
-
next_cursor: z.string().nullable(),
|
|
2241
|
-
});
|
|
2242
2268
|
export const ListChannelsResponseSchema = z.strictObject({
|
|
2243
2269
|
items: z.array(ChannelSchema),
|
|
2244
2270
|
next_cursor: z.string().nullable(),
|
|
@@ -2259,12 +2285,42 @@ export const ListSessionThreadsResponseSchema = z.strictObject({
|
|
|
2259
2285
|
threads: z.array(SessionThreadSchema).max(50),
|
|
2260
2286
|
});
|
|
2261
2287
|
export const ListWorkTypesResponseSchema = z.strictObject({ items: z.array(WorkTypeSchema) });
|
|
2262
|
-
export const
|
|
2263
|
-
|
|
2264
|
-
|
|
2288
|
+
export const MessageSchema = z.strictObject({
|
|
2289
|
+
id: UuidSchema,
|
|
2290
|
+
channel_id: UuidSchema,
|
|
2291
|
+
author: MessageAuthorSchema.nullable(),
|
|
2292
|
+
kind: MessageKindSchema,
|
|
2293
|
+
body: z.string(),
|
|
2294
|
+
code: z.string().nullable(),
|
|
2295
|
+
code_language: z.string().nullable(),
|
|
2296
|
+
file: z
|
|
2297
|
+
.strictObject({
|
|
2298
|
+
id: UuidSchema,
|
|
2299
|
+
name: z.string(),
|
|
2300
|
+
type: z.string(),
|
|
2301
|
+
duration_ms: z.int().min(1).nullable(),
|
|
2302
|
+
})
|
|
2303
|
+
.nullable(),
|
|
2304
|
+
photos: z.array(z.strictObject({
|
|
2305
|
+
id: UuidSchema,
|
|
2306
|
+
name: z.string(),
|
|
2307
|
+
type: z.string(),
|
|
2308
|
+
alt: z.string().nullable(),
|
|
2309
|
+
duration_ms: z.int().min(1).nullable(),
|
|
2310
|
+
})),
|
|
2311
|
+
mentions: z.array(UserSchema),
|
|
2312
|
+
reply_count: z.int(),
|
|
2313
|
+
last_reply_at: TimestampSchema.nullable(),
|
|
2314
|
+
created_at: TimestampSchema,
|
|
2315
|
+
edited_at: TimestampSchema.nullable(),
|
|
2316
|
+
deleted: z.boolean(),
|
|
2317
|
+
via_agent: z.string().nullable(),
|
|
2318
|
+
pinned: z.boolean(),
|
|
2319
|
+
pinned_at: TimestampSchema.nullable(),
|
|
2265
2320
|
pinned_by: MessageAuthorSchema.nullable(),
|
|
2266
|
-
|
|
2321
|
+
item: MessageItemSchema.optional(),
|
|
2267
2322
|
});
|
|
2323
|
+
export const PageResponseSchema = z.strictObject({ page: PageSchema });
|
|
2268
2324
|
export const PlanResultSchema = z.union([
|
|
2269
2325
|
z.strictObject({ action: z.literal('delete_item'), deleted: z.array(ItemBriefSchema) }),
|
|
2270
2326
|
z.strictObject({
|
|
@@ -2431,11 +2487,13 @@ export const SessionItemUnassignedDataSchema = z.strictObject({
|
|
|
2431
2487
|
]),
|
|
2432
2488
|
user: SessionEventUserSchema.nullable(),
|
|
2433
2489
|
});
|
|
2434
|
-
export const
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2490
|
+
export const SessionRequestContextSchema = z.union([
|
|
2491
|
+
SessionRequestChannelContextSchema,
|
|
2492
|
+
SessionRequestThreadContextSchema,
|
|
2493
|
+
SessionRequestItemContextSchema,
|
|
2494
|
+
SessionRequestItemsContextSchema,
|
|
2495
|
+
SessionRequestBriefContextSchema,
|
|
2496
|
+
]);
|
|
2439
2497
|
export const TransitionItemRequestSchema = z.strictObject({
|
|
2440
2498
|
status: NameRefSchema,
|
|
2441
2499
|
comment: CommentBodySchema.optional(),
|
|
@@ -2539,10 +2597,15 @@ export const ItemSearchResultSchema = z.strictObject({
|
|
|
2539
2597
|
description: z.string().optional(),
|
|
2540
2598
|
custom: z.array(CustomFieldValueSchema).optional(),
|
|
2541
2599
|
});
|
|
2542
|
-
export const
|
|
2543
|
-
items: z.array(
|
|
2600
|
+
export const ListChannelMessagesResponseSchema = z.strictObject({
|
|
2601
|
+
items: z.array(MessageSchema),
|
|
2544
2602
|
next_cursor: z.string().nullable(),
|
|
2545
2603
|
});
|
|
2604
|
+
export const PinnedMessageSchema = z.strictObject({
|
|
2605
|
+
pinned_at: TimestampSchema,
|
|
2606
|
+
pinned_by: MessageAuthorSchema.nullable(),
|
|
2607
|
+
message: MessageSchema,
|
|
2608
|
+
});
|
|
2546
2609
|
export const ProposeSessionHandoffResponseSchema = z.strictObject({
|
|
2547
2610
|
handoff: SessionHandoffSchema,
|
|
2548
2611
|
created: z.boolean(),
|
|
@@ -2605,6 +2668,37 @@ export const SessionPlanOutcomeSchema = z.strictObject({
|
|
|
2605
2668
|
error: z.strictObject({ code: z.string(), message: z.string().optional() }).optional(),
|
|
2606
2669
|
changed: z.array(z.strictObject({ kind: z.string(), ref: z.string() })).optional(),
|
|
2607
2670
|
});
|
|
2671
|
+
export const SessionRequestDataSchema = z.strictObject({
|
|
2672
|
+
request: z.strictObject({
|
|
2673
|
+
id: UuidSchema,
|
|
2674
|
+
kind: z.enum([
|
|
2675
|
+
'catch_up',
|
|
2676
|
+
'thread_to_items',
|
|
2677
|
+
'triage',
|
|
2678
|
+
'item_helper.spec',
|
|
2679
|
+
'item_helper.subtasks',
|
|
2680
|
+
'item_helper.estimate',
|
|
2681
|
+
'item_helper.related',
|
|
2682
|
+
'item_helper.stalled',
|
|
2683
|
+
]),
|
|
2684
|
+
source: z.enum(['button', 'subscription']),
|
|
2685
|
+
subscription_id: UuidSchema.nullable(),
|
|
2686
|
+
created_at: TimestampSchema,
|
|
2687
|
+
expires_at: TimestampSchema,
|
|
2688
|
+
}),
|
|
2689
|
+
requester: SessionEventUserSchema,
|
|
2690
|
+
locale: z.string(),
|
|
2691
|
+
time_zone: z.string(),
|
|
2692
|
+
project: SessionEventProjectSchema.nullable(),
|
|
2693
|
+
context: SessionRequestContextSchema,
|
|
2694
|
+
result: z.strictObject({ place: z.enum(['dm', 'thread', 'item_thread']) }),
|
|
2695
|
+
handed: z.strictObject({ from_session: SessionBriefSchema }).nullable(),
|
|
2696
|
+
});
|
|
2697
|
+
export const ThreadResponseSchema = z.strictObject({
|
|
2698
|
+
root: MessageSchema,
|
|
2699
|
+
replies: z.array(MessageSchema),
|
|
2700
|
+
next_cursor: z.string().nullable(),
|
|
2701
|
+
});
|
|
2608
2702
|
export const CreateItemResponseSchema = z.strictObject({
|
|
2609
2703
|
item: ItemDetailSchema,
|
|
2610
2704
|
created: z.boolean(),
|
|
@@ -2620,6 +2714,10 @@ export const GetItemResponseSchema = z.strictObject({
|
|
|
2620
2714
|
feedback: FeedbackSchema.optional(),
|
|
2621
2715
|
});
|
|
2622
2716
|
export const ItemWriteResponseSchema = z.strictObject({ item: ItemDetailSchema });
|
|
2717
|
+
export const ListPinsResponseSchema = z.strictObject({
|
|
2718
|
+
items: z.array(PinnedMessageSchema),
|
|
2719
|
+
next_cursor: z.string().nullable(),
|
|
2720
|
+
});
|
|
2623
2721
|
export const SearchItemsResponseSchema = z.strictObject({
|
|
2624
2722
|
items: z.array(ItemSearchResultSchema),
|
|
2625
2723
|
next_cursor: z.string().nullable(),
|
|
@@ -2679,6 +2777,17 @@ export const SessionMessageEventSchema = z.strictObject({
|
|
|
2679
2777
|
session: SessionBriefSchema,
|
|
2680
2778
|
data: SessionEventDataSchema,
|
|
2681
2779
|
});
|
|
2780
|
+
export const SessionRequestEventSchema = z.strictObject({
|
|
2781
|
+
v: z.literal(1),
|
|
2782
|
+
id: UuidSchema,
|
|
2783
|
+
type: z.literal('agent.request'),
|
|
2784
|
+
created_at: TimestampSchema,
|
|
2785
|
+
org: z.strictObject({ id: UuidSchema }),
|
|
2786
|
+
app: z.strictObject({ id: UuidSchema, installation_id: UuidSchema }),
|
|
2787
|
+
attempt: z.int().min(1),
|
|
2788
|
+
session: SessionBriefSchema,
|
|
2789
|
+
data: SessionRequestDataSchema,
|
|
2790
|
+
});
|
|
2682
2791
|
export const SessionCardClosedEventSchema = z.strictObject({
|
|
2683
2792
|
v: z.literal(1),
|
|
2684
2793
|
id: UuidSchema,
|
|
@@ -2698,6 +2807,7 @@ export const SessionEventSchema = z.union([
|
|
|
2698
2807
|
SessionItemAssignedEventSchema,
|
|
2699
2808
|
SessionItemUnassignedEventSchema,
|
|
2700
2809
|
SessionChildDoneEventSchema,
|
|
2810
|
+
SessionRequestEventSchema,
|
|
2701
2811
|
]);
|
|
2702
2812
|
export const SessionEventsResponseSchema = z.strictObject({
|
|
2703
2813
|
events: z.array(SessionEventSchema),
|
|
@@ -2758,6 +2868,7 @@ export const QUERY_SCHEMAS = {
|
|
|
2758
2868
|
get_workflow: z.strictObject({}),
|
|
2759
2869
|
list_channel_messages: z.strictObject({
|
|
2760
2870
|
since: TimestampSchema.optional(),
|
|
2871
|
+
active_since: TimestampSchema.optional(),
|
|
2761
2872
|
cursor: CursorSchema.optional(),
|
|
2762
2873
|
limit: z.coerce.number().int().min(1).max(100).optional(),
|
|
2763
2874
|
}),
|
|
@@ -2768,6 +2879,7 @@ export const QUERY_SCHEMAS = {
|
|
|
2768
2879
|
}),
|
|
2769
2880
|
list_comments: z.strictObject({
|
|
2770
2881
|
order: z.enum(['asc', 'desc']).optional(),
|
|
2882
|
+
since: TimestampSchema.optional(),
|
|
2771
2883
|
cursor: CursorSchema.optional(),
|
|
2772
2884
|
limit: z.coerce.number().int().min(1).max(100).optional(),
|
|
2773
2885
|
}),
|
package/dist/channel/loop.js
CHANGED
|
@@ -13,6 +13,11 @@
|
|
|
13
13
|
* `mention` (the owner mentioned it in the item's thread), `message` (the
|
|
14
14
|
* owner's other replies in a thread it works in) and `item_unassigned`
|
|
15
15
|
* (stop: it also stops "is writing" and the waiters of that thread).
|
|
16
|
+
* A request (`agent.request`, requests.ts) becomes `event="request"`, with
|
|
17
|
+
* its kind, context ids and the owner's language and time zone as
|
|
18
|
+
* attributes and a lead line written here from structured fields; only
|
|
19
|
+
* this session's, only the owner's, and once per request (a hand-on to
|
|
20
|
+
* another session writes a new event for the same request).
|
|
16
21
|
* Session handoffs (handoffs.ts): an assignment that came by handoff is an
|
|
17
22
|
* `item_assigned` with `handoff="true"`; `work_item.child_done` becomes
|
|
18
23
|
* `child_done` (a subtask this session handed on is in review or done, was
|
|
@@ -51,6 +56,7 @@ import { ackSessionEvents, getSession, listSessionEvents, listSessionThreads, SE
|
|
|
51
56
|
import { apiApprovalCloser } from './approvals.js';
|
|
52
57
|
import { attachmentsBlock, forwardedFilesLead } from './attachment-blocks.js';
|
|
53
58
|
import { AttachmentStore, filesServed } from './attachments.js';
|
|
59
|
+
import { REQUEST_EVENT_TYPE, requestContent, } from './requests.js';
|
|
54
60
|
import { ToolRing } from './tool-ring.js';
|
|
55
61
|
import { CardWaiters, eventMessageId } from './waiters.js';
|
|
56
62
|
/** The notification Claude Code reads as a channel event. */
|
|
@@ -63,6 +69,8 @@ const ENDED_NOTICES = {
|
|
|
63
69
|
};
|
|
64
70
|
/** How many item keys are remembered (key to id). */
|
|
65
71
|
const ITEMS_MAX = 500;
|
|
72
|
+
/** How many requests are remembered (id to kind and place), for reply's check. */
|
|
73
|
+
const REQUESTS_MAX = 200;
|
|
66
74
|
/**
|
|
67
75
|
* What the loop and the channel tools (reply, ask, the plan cards) share:
|
|
68
76
|
* the session, the thread of the latest message, the items seen in events,
|
|
@@ -129,6 +137,19 @@ export class ChannelState {
|
|
|
129
137
|
: [{ thread_id: this.lastDmThread, item: undefined }]),
|
|
130
138
|
];
|
|
131
139
|
}
|
|
140
|
+
/** The requests this session was sent, by id (the oldest forgotten past REQUESTS_MAX). */
|
|
141
|
+
requests = new Map();
|
|
142
|
+
/** Remembers a request's kind and where its result goes. */
|
|
143
|
+
rememberRequest(id, request) {
|
|
144
|
+
const k = id.toLowerCase();
|
|
145
|
+
this.requests.delete(k);
|
|
146
|
+
this.requests.set(k, request);
|
|
147
|
+
if (this.requests.size > REQUESTS_MAX) {
|
|
148
|
+
const oldest = this.requests.keys().next().value;
|
|
149
|
+
if (oldest !== undefined)
|
|
150
|
+
this.requests.delete(oldest);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
132
153
|
/** The owner's locale, from their latest event (the cards' language). */
|
|
133
154
|
ownerLocale;
|
|
134
155
|
/**
|
|
@@ -533,6 +554,10 @@ export class ChannelLoop {
|
|
|
533
554
|
return;
|
|
534
555
|
}
|
|
535
556
|
this.seen.add(event.id);
|
|
557
|
+
if (event.type === REQUEST_EVENT_TYPE) {
|
|
558
|
+
await this.deliverRequest(event, info);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
536
561
|
if (event.type === 'card.response' || event.type === 'card.closed') {
|
|
537
562
|
await this.deliverCard(event, info);
|
|
538
563
|
return;
|
|
@@ -614,6 +639,51 @@ export class ChannelLoop {
|
|
|
614
639
|
this.options.state.typing?.start(threadId);
|
|
615
640
|
logger.info('channel message delivered', { event_id: event.id, message_id: message.id });
|
|
616
641
|
}
|
|
642
|
+
/**
|
|
643
|
+
* A request (agent.request): the owner's tap on a "... with my agent"
|
|
644
|
+
* button, or a subscription they set up. Dropped when it is another
|
|
645
|
+
* session's (other_session), not the owner's (not_owner: the server sends
|
|
646
|
+
* only the owner's, and this checks again), a request already handed on
|
|
647
|
+
* (repeat: by request id, since a hand-on writes a new event), or a shape
|
|
648
|
+
* this version can't read (shape).
|
|
649
|
+
*/
|
|
650
|
+
async deliverRequest(event, info) {
|
|
651
|
+
const { logger, state } = this.options;
|
|
652
|
+
if (event.session.id !== info.session.id) {
|
|
653
|
+
logger.warn('channel event dropped', { event_id: event.id, reason: 'other_session' });
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
const { requester, request } = event.data;
|
|
657
|
+
if (requester.id !== info.owner.id || requester.is_guest) {
|
|
658
|
+
logger.warn('channel event dropped', { event_id: event.id, reason: 'not_owner' });
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
const seenKey = `r:${request.id.toLowerCase()}`;
|
|
662
|
+
if (this.seen.has(seenKey)) {
|
|
663
|
+
logger.debug('channel event dropped', { event_id: event.id, reason: 'repeat' });
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
const built = requestContent(event.data, {
|
|
667
|
+
owner: info.owner.name,
|
|
668
|
+
session: event.session.label,
|
|
669
|
+
});
|
|
670
|
+
if (!built.ok) {
|
|
671
|
+
logger.warn('channel event dropped', { event_id: event.id, reason: built.reason });
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
this.seen.add(seenKey);
|
|
675
|
+
this.noteLocale({ locale: built.meta.locale });
|
|
676
|
+
await this.send({ content: built.content, meta: built.meta });
|
|
677
|
+
state.rememberRequest(built.requestId, {
|
|
678
|
+
kind: request.kind,
|
|
679
|
+
place: built.meta.result,
|
|
680
|
+
});
|
|
681
|
+
logger.info('request delivered', {
|
|
682
|
+
event_id: event.id,
|
|
683
|
+
request_id: built.requestId,
|
|
684
|
+
kind: request.kind,
|
|
685
|
+
});
|
|
686
|
+
}
|
|
617
687
|
/** Sends one notification; a failure stops the loop (the page is not acknowledged). */
|
|
618
688
|
async send(params) {
|
|
619
689
|
try {
|