larkin 0.3.3 → 0.4.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/agent/agent-state-store.cjs +127 -21
- package/dist/agent/agent-state-store.mjs +128 -22
- package/dist/agent/agent-transport.cjs +6 -4
- package/dist/agent/agent-transport.mjs +6 -4
- package/dist/agent/host-reminder-orchestrator.cjs +9 -3
- package/dist/agent/host-reminder-orchestrator.mjs +9 -3
- package/dist/agent/inbox-projection.cjs +114 -23
- package/dist/agent/inbox-projection.mjs +112 -23
- package/dist/feishu/document-comment.cjs +2 -1
- package/dist/feishu/document-comment.mjs +2 -1
- package/dist/feishu/host-business-state.cjs +7 -2
- package/dist/feishu/host-business-state.mjs +7 -2
- package/dist/feishu/host-shell.cjs +66 -31
- package/dist/feishu/host-shell.mjs +66 -31
- package/dist/runtime/runtime-host.cjs +173 -10
- package/dist/runtime/runtime-host.mjs +173 -10
- package/package.json +1 -1
|
@@ -1,22 +1,118 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RUNTIME_REDELIVERY_TARGET = exports.RUNTIME_REMINDER_TARGET = void 0;
|
|
4
|
+
exports.isCanonicalInboxTarget = isCanonicalInboxTarget;
|
|
3
5
|
exports.targetKeyOfInboxEnvelope = targetKeyOfInboxEnvelope;
|
|
4
6
|
exports.projectInboxEnvelope = projectInboxEnvelope;
|
|
5
7
|
exports.targetOfInboxEnvelope = targetOfInboxEnvelope;
|
|
6
8
|
exports.projectInboxEvents = projectInboxEvents;
|
|
7
9
|
exports.projectInboxCheck = projectInboxCheck;
|
|
10
|
+
exports.RUNTIME_REMINDER_TARGET = "runtime:reminder";
|
|
11
|
+
exports.RUNTIME_REDELIVERY_TARGET = "runtime:redelivery";
|
|
12
|
+
const CHAT_TARGET_PREFIX = "chat:";
|
|
13
|
+
const THREAD_TARGET_PREFIX = "thread:";
|
|
14
|
+
const DOCUMENT_COMMENT_TARGET_PREFIX = "document-comment:";
|
|
15
|
+
const REMINDER_MESSAGE_PREFIX = "rem_";
|
|
16
|
+
const REDELIVERY_MESSAGE_PREFIX = "redeliver_";
|
|
17
|
+
function hasNonemptySuffix(value, prefix) {
|
|
18
|
+
return value.startsWith(prefix) && value.length > prefix.length;
|
|
19
|
+
}
|
|
20
|
+
function internalTargetOfInboxEnvelope(envelope) {
|
|
21
|
+
const messageId = typeof envelope.message_id === "string" ? envelope.message_id : "";
|
|
22
|
+
const reminderKind = envelope.kind === "reminder";
|
|
23
|
+
const redeliveryKind = envelope.kind === "redelivery";
|
|
24
|
+
const reminderMarker = messageId.startsWith(REMINDER_MESSAGE_PREFIX);
|
|
25
|
+
const redeliveryMarker = messageId.startsWith(REDELIVERY_MESSAGE_PREFIX);
|
|
26
|
+
const reminderId = hasNonemptySuffix(messageId, REMINDER_MESSAGE_PREFIX);
|
|
27
|
+
const redeliveryId = hasNonemptySuffix(messageId, REDELIVERY_MESSAGE_PREFIX);
|
|
28
|
+
const hasInternalMarker = reminderKind || redeliveryKind || reminderMarker || redeliveryMarker;
|
|
29
|
+
if (!hasInternalMarker)
|
|
30
|
+
return null;
|
|
31
|
+
if (reminderKind && reminderId && !redeliveryKind && !redeliveryMarker)
|
|
32
|
+
return exports.RUNTIME_REMINDER_TARGET;
|
|
33
|
+
if (redeliveryKind && redeliveryId && !reminderKind && !reminderMarker)
|
|
34
|
+
return exports.RUNTIME_REDELIVERY_TARGET;
|
|
35
|
+
throw new Error(`Inbox internal source requires matching kind and message_id prefix; received kind=${JSON.stringify(envelope.kind ?? null)} message_id=${JSON.stringify(envelope.message_id ?? null)}`);
|
|
36
|
+
}
|
|
37
|
+
function isChatTarget(target) {
|
|
38
|
+
return hasNonemptySuffix(target, CHAT_TARGET_PREFIX);
|
|
39
|
+
}
|
|
40
|
+
function isThreadTarget(target) {
|
|
41
|
+
return hasNonemptySuffix(target, THREAD_TARGET_PREFIX);
|
|
42
|
+
}
|
|
43
|
+
function isDocumentCommentTarget(target) {
|
|
44
|
+
return hasNonemptySuffix(target, DOCUMENT_COMMENT_TARGET_PREFIX);
|
|
45
|
+
}
|
|
46
|
+
function isCanonicalInboxTarget(target) {
|
|
47
|
+
return isChatTarget(target)
|
|
48
|
+
|| isThreadTarget(target)
|
|
49
|
+
|| target === exports.RUNTIME_REMINDER_TARGET
|
|
50
|
+
|| target === exports.RUNTIME_REDELIVERY_TARGET
|
|
51
|
+
|| isDocumentCommentTarget(target);
|
|
52
|
+
}
|
|
53
|
+
function invalidTarget(target) {
|
|
54
|
+
return new Error(`Invalid canonical Inbox target ${JSON.stringify(target)}; expected chat:<nonempty>, thread:<nonempty>, runtime:reminder, runtime:redelivery, or document-comment:<nonempty>`);
|
|
55
|
+
}
|
|
56
|
+
function optionalLocator(envelope, key) {
|
|
57
|
+
const value = envelope[key];
|
|
58
|
+
if (value === undefined || value === null || value === "")
|
|
59
|
+
return "";
|
|
60
|
+
if (typeof value !== "string")
|
|
61
|
+
throw new Error(`Inbox ${key} must be a string when present; received ${JSON.stringify(value)}`);
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
8
64
|
function targetKeyOfInboxEnvelope(envelope) {
|
|
9
65
|
if (!envelope)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
66
|
+
throw new Error("Inbox envelope is required to derive a canonical target");
|
|
67
|
+
const internalTarget = internalTargetOfInboxEnvelope(envelope);
|
|
68
|
+
const chatId = optionalLocator(envelope, "chat_id");
|
|
69
|
+
const threadId = optionalLocator(envelope, "thread_id");
|
|
70
|
+
if (threadId && !chatId) {
|
|
71
|
+
throw new Error(`Inbox thread locator requires nonempty chat_id and thread_id; received chat_id=${JSON.stringify(chatId)} thread_id=${JSON.stringify(threadId)}`);
|
|
13
72
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
73
|
+
const locatorTarget = chatId ? (threadId ? `thread:${chatId}:${threadId}` : `chat:${chatId}`) : null;
|
|
74
|
+
const hasExplicitTarget = Object.prototype.hasOwnProperty.call(envelope, "target");
|
|
75
|
+
if (hasExplicitTarget) {
|
|
76
|
+
if (typeof envelope.target !== "string" || envelope.target.length === 0)
|
|
77
|
+
throw invalidTarget(String(envelope.target));
|
|
78
|
+
const target = envelope.target;
|
|
79
|
+
if (!isCanonicalInboxTarget(target))
|
|
80
|
+
throw invalidTarget(target);
|
|
81
|
+
if (isChatTarget(target) || isThreadTarget(target)) {
|
|
82
|
+
if (internalTarget || envelope.kind === "document_comment")
|
|
83
|
+
throw new Error(`Inbox chat/thread target conflicts with source kind/message_id`);
|
|
84
|
+
if (locatorTarget && locatorTarget !== target) {
|
|
85
|
+
throw new Error(`Inbox chat/thread target conflicts with locator target ${JSON.stringify(locatorTarget)}`);
|
|
86
|
+
}
|
|
87
|
+
return target;
|
|
88
|
+
}
|
|
89
|
+
if (target === exports.RUNTIME_REMINDER_TARGET || target === exports.RUNTIME_REDELIVERY_TARGET) {
|
|
90
|
+
if (locatorTarget)
|
|
91
|
+
throw new Error(`Inbox runtime target cannot carry chat_id/thread_id locators`);
|
|
92
|
+
if (internalTarget !== target)
|
|
93
|
+
throw new Error(`Inbox target ${target} requires exact matching kind and message_id prefix`);
|
|
94
|
+
return target;
|
|
95
|
+
}
|
|
96
|
+
if (isDocumentCommentTarget(target)) {
|
|
97
|
+
if (envelope.kind !== "document_comment" || internalTarget)
|
|
98
|
+
throw new Error(`Inbox document-comment target requires kind=document_comment and no internal source`);
|
|
99
|
+
if (locatorTarget)
|
|
100
|
+
throw new Error(`Inbox document-comment target cannot carry chat_id/thread_id locators`);
|
|
101
|
+
return target;
|
|
102
|
+
}
|
|
103
|
+
throw invalidTarget(target);
|
|
18
104
|
}
|
|
19
|
-
|
|
105
|
+
if (envelope.kind === "document_comment") {
|
|
106
|
+
throw new Error("Inbox document_comment requires an explicit document-comment:<nonempty> target");
|
|
107
|
+
}
|
|
108
|
+
if (locatorTarget) {
|
|
109
|
+
if (internalTarget)
|
|
110
|
+
throw new Error(`Inbox internal source cannot be derived as a chat/thread target`);
|
|
111
|
+
return locatorTarget;
|
|
112
|
+
}
|
|
113
|
+
if (internalTarget)
|
|
114
|
+
throw new Error(`Inbox internal source requires an explicit ${internalTarget} target`);
|
|
115
|
+
throw new Error(`Inbox envelope has no canonical target locator (message_id=${JSON.stringify(envelope.message_id ?? null)}, kind=${JSON.stringify(envelope.kind ?? null)})`);
|
|
20
116
|
}
|
|
21
117
|
/** Enrich the durable local Inbox without changing the canonical Runtime envelope. */
|
|
22
118
|
function projectInboxEnvelope(canonical, locators) {
|
|
@@ -32,22 +128,14 @@ function projectInboxEnvelope(canonical, locators) {
|
|
|
32
128
|
content: locators.content !== undefined ? locators.content : (typeof canonical.content === "string" ? canonical.content : ""),
|
|
33
129
|
};
|
|
34
130
|
}
|
|
35
|
-
/**
|
|
131
|
+
/** Return an allowlisted canonical target when the envelope is locatable. */
|
|
36
132
|
function targetOfInboxEnvelope(envelope) {
|
|
37
|
-
|
|
38
|
-
return
|
|
39
|
-
if (envelope.kind === "document_comment") {
|
|
40
|
-
return typeof envelope.target === "string" && envelope.target ? envelope.target : null;
|
|
133
|
+
try {
|
|
134
|
+
return targetKeyOfInboxEnvelope(envelope);
|
|
41
135
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
? `dm:@${String(envelope.parent_channel_name)}`
|
|
45
|
-
: `#${String(envelope.parent_channel_name)}`;
|
|
46
|
-
return `${base}:${String(envelope.channel_name || "").slice(0, 8)}`;
|
|
136
|
+
catch {
|
|
137
|
+
return null;
|
|
47
138
|
}
|
|
48
|
-
return envelope.channel_type === "dm"
|
|
49
|
-
? `dm:@${String(envelope.channel_name)}`
|
|
50
|
-
: `#${String(envelope.channel_name)}`;
|
|
51
139
|
}
|
|
52
140
|
/** Project persisted inbox envelopes into the exact agentApi events response data shape. */
|
|
53
141
|
function projectInboxEvents(envelopes) {
|
|
@@ -56,7 +144,7 @@ function projectInboxEvents(envelopes) {
|
|
|
56
144
|
events: envelopes,
|
|
57
145
|
last_seen_msgId: last ? (last.message_id || null) : null,
|
|
58
146
|
last_seen_seq: last ? (last.seq ?? null) : null,
|
|
59
|
-
reply_target:
|
|
147
|
+
reply_target: last ? targetKeyOfInboxEnvelope(last) : null,
|
|
60
148
|
pending_notice_ids: [],
|
|
61
149
|
wake_reason: null,
|
|
62
150
|
has_more: false,
|
|
@@ -64,9 +152,12 @@ function projectInboxEvents(envelopes) {
|
|
|
64
152
|
}
|
|
65
153
|
/** Bounded, content-light and deterministic projection for the read-only check command. */
|
|
66
154
|
function projectInboxCheck(envelopes, onlyTarget) {
|
|
155
|
+
if (onlyTarget && !isCanonicalInboxTarget(onlyTarget)) {
|
|
156
|
+
throw new Error(`Invalid canonical Inbox check target ${JSON.stringify(onlyTarget)}`);
|
|
157
|
+
}
|
|
67
158
|
const byTarget = new Map();
|
|
68
159
|
for (const envelope of envelopes) {
|
|
69
|
-
const target =
|
|
160
|
+
const target = targetKeyOfInboxEnvelope(envelope);
|
|
70
161
|
if (onlyTarget && onlyTarget !== target)
|
|
71
162
|
continue;
|
|
72
163
|
const rows = byTarget.get(target) ?? [];
|
|
@@ -1,15 +1,109 @@
|
|
|
1
|
+
export const RUNTIME_REMINDER_TARGET = "runtime:reminder";
|
|
2
|
+
export const RUNTIME_REDELIVERY_TARGET = "runtime:redelivery";
|
|
3
|
+
const CHAT_TARGET_PREFIX = "chat:";
|
|
4
|
+
const THREAD_TARGET_PREFIX = "thread:";
|
|
5
|
+
const DOCUMENT_COMMENT_TARGET_PREFIX = "document-comment:";
|
|
6
|
+
const REMINDER_MESSAGE_PREFIX = "rem_";
|
|
7
|
+
const REDELIVERY_MESSAGE_PREFIX = "redeliver_";
|
|
8
|
+
function hasNonemptySuffix(value, prefix) {
|
|
9
|
+
return value.startsWith(prefix) && value.length > prefix.length;
|
|
10
|
+
}
|
|
11
|
+
function internalTargetOfInboxEnvelope(envelope) {
|
|
12
|
+
const messageId = typeof envelope.message_id === "string" ? envelope.message_id : "";
|
|
13
|
+
const reminderKind = envelope.kind === "reminder";
|
|
14
|
+
const redeliveryKind = envelope.kind === "redelivery";
|
|
15
|
+
const reminderMarker = messageId.startsWith(REMINDER_MESSAGE_PREFIX);
|
|
16
|
+
const redeliveryMarker = messageId.startsWith(REDELIVERY_MESSAGE_PREFIX);
|
|
17
|
+
const reminderId = hasNonemptySuffix(messageId, REMINDER_MESSAGE_PREFIX);
|
|
18
|
+
const redeliveryId = hasNonemptySuffix(messageId, REDELIVERY_MESSAGE_PREFIX);
|
|
19
|
+
const hasInternalMarker = reminderKind || redeliveryKind || reminderMarker || redeliveryMarker;
|
|
20
|
+
if (!hasInternalMarker)
|
|
21
|
+
return null;
|
|
22
|
+
if (reminderKind && reminderId && !redeliveryKind && !redeliveryMarker)
|
|
23
|
+
return RUNTIME_REMINDER_TARGET;
|
|
24
|
+
if (redeliveryKind && redeliveryId && !reminderKind && !reminderMarker)
|
|
25
|
+
return RUNTIME_REDELIVERY_TARGET;
|
|
26
|
+
throw new Error(`Inbox internal source requires matching kind and message_id prefix; received kind=${JSON.stringify(envelope.kind ?? null)} message_id=${JSON.stringify(envelope.message_id ?? null)}`);
|
|
27
|
+
}
|
|
28
|
+
function isChatTarget(target) {
|
|
29
|
+
return hasNonemptySuffix(target, CHAT_TARGET_PREFIX);
|
|
30
|
+
}
|
|
31
|
+
function isThreadTarget(target) {
|
|
32
|
+
return hasNonemptySuffix(target, THREAD_TARGET_PREFIX);
|
|
33
|
+
}
|
|
34
|
+
function isDocumentCommentTarget(target) {
|
|
35
|
+
return hasNonemptySuffix(target, DOCUMENT_COMMENT_TARGET_PREFIX);
|
|
36
|
+
}
|
|
37
|
+
export function isCanonicalInboxTarget(target) {
|
|
38
|
+
return isChatTarget(target)
|
|
39
|
+
|| isThreadTarget(target)
|
|
40
|
+
|| target === RUNTIME_REMINDER_TARGET
|
|
41
|
+
|| target === RUNTIME_REDELIVERY_TARGET
|
|
42
|
+
|| isDocumentCommentTarget(target);
|
|
43
|
+
}
|
|
44
|
+
function invalidTarget(target) {
|
|
45
|
+
return new Error(`Invalid canonical Inbox target ${JSON.stringify(target)}; expected chat:<nonempty>, thread:<nonempty>, runtime:reminder, runtime:redelivery, or document-comment:<nonempty>`);
|
|
46
|
+
}
|
|
47
|
+
function optionalLocator(envelope, key) {
|
|
48
|
+
const value = envelope[key];
|
|
49
|
+
if (value === undefined || value === null || value === "")
|
|
50
|
+
return "";
|
|
51
|
+
if (typeof value !== "string")
|
|
52
|
+
throw new Error(`Inbox ${key} must be a string when present; received ${JSON.stringify(value)}`);
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
1
55
|
export function targetKeyOfInboxEnvelope(envelope) {
|
|
2
56
|
if (!envelope)
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
57
|
+
throw new Error("Inbox envelope is required to derive a canonical target");
|
|
58
|
+
const internalTarget = internalTargetOfInboxEnvelope(envelope);
|
|
59
|
+
const chatId = optionalLocator(envelope, "chat_id");
|
|
60
|
+
const threadId = optionalLocator(envelope, "thread_id");
|
|
61
|
+
if (threadId && !chatId) {
|
|
62
|
+
throw new Error(`Inbox thread locator requires nonempty chat_id and thread_id; received chat_id=${JSON.stringify(chatId)} thread_id=${JSON.stringify(threadId)}`);
|
|
6
63
|
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
64
|
+
const locatorTarget = chatId ? (threadId ? `thread:${chatId}:${threadId}` : `chat:${chatId}`) : null;
|
|
65
|
+
const hasExplicitTarget = Object.prototype.hasOwnProperty.call(envelope, "target");
|
|
66
|
+
if (hasExplicitTarget) {
|
|
67
|
+
if (typeof envelope.target !== "string" || envelope.target.length === 0)
|
|
68
|
+
throw invalidTarget(String(envelope.target));
|
|
69
|
+
const target = envelope.target;
|
|
70
|
+
if (!isCanonicalInboxTarget(target))
|
|
71
|
+
throw invalidTarget(target);
|
|
72
|
+
if (isChatTarget(target) || isThreadTarget(target)) {
|
|
73
|
+
if (internalTarget || envelope.kind === "document_comment")
|
|
74
|
+
throw new Error(`Inbox chat/thread target conflicts with source kind/message_id`);
|
|
75
|
+
if (locatorTarget && locatorTarget !== target) {
|
|
76
|
+
throw new Error(`Inbox chat/thread target conflicts with locator target ${JSON.stringify(locatorTarget)}`);
|
|
77
|
+
}
|
|
78
|
+
return target;
|
|
79
|
+
}
|
|
80
|
+
if (target === RUNTIME_REMINDER_TARGET || target === RUNTIME_REDELIVERY_TARGET) {
|
|
81
|
+
if (locatorTarget)
|
|
82
|
+
throw new Error(`Inbox runtime target cannot carry chat_id/thread_id locators`);
|
|
83
|
+
if (internalTarget !== target)
|
|
84
|
+
throw new Error(`Inbox target ${target} requires exact matching kind and message_id prefix`);
|
|
85
|
+
return target;
|
|
86
|
+
}
|
|
87
|
+
if (isDocumentCommentTarget(target)) {
|
|
88
|
+
if (envelope.kind !== "document_comment" || internalTarget)
|
|
89
|
+
throw new Error(`Inbox document-comment target requires kind=document_comment and no internal source`);
|
|
90
|
+
if (locatorTarget)
|
|
91
|
+
throw new Error(`Inbox document-comment target cannot carry chat_id/thread_id locators`);
|
|
92
|
+
return target;
|
|
93
|
+
}
|
|
94
|
+
throw invalidTarget(target);
|
|
11
95
|
}
|
|
12
|
-
|
|
96
|
+
if (envelope.kind === "document_comment") {
|
|
97
|
+
throw new Error("Inbox document_comment requires an explicit document-comment:<nonempty> target");
|
|
98
|
+
}
|
|
99
|
+
if (locatorTarget) {
|
|
100
|
+
if (internalTarget)
|
|
101
|
+
throw new Error(`Inbox internal source cannot be derived as a chat/thread target`);
|
|
102
|
+
return locatorTarget;
|
|
103
|
+
}
|
|
104
|
+
if (internalTarget)
|
|
105
|
+
throw new Error(`Inbox internal source requires an explicit ${internalTarget} target`);
|
|
106
|
+
throw new Error(`Inbox envelope has no canonical target locator (message_id=${JSON.stringify(envelope.message_id ?? null)}, kind=${JSON.stringify(envelope.kind ?? null)})`);
|
|
13
107
|
}
|
|
14
108
|
/** Enrich the durable local Inbox without changing the canonical Runtime envelope. */
|
|
15
109
|
export function projectInboxEnvelope(canonical, locators) {
|
|
@@ -25,22 +119,14 @@ export function projectInboxEnvelope(canonical, locators) {
|
|
|
25
119
|
content: locators.content !== undefined ? locators.content : (typeof canonical.content === "string" ? canonical.content : ""),
|
|
26
120
|
};
|
|
27
121
|
}
|
|
28
|
-
/**
|
|
122
|
+
/** Return an allowlisted canonical target when the envelope is locatable. */
|
|
29
123
|
export function targetOfInboxEnvelope(envelope) {
|
|
30
|
-
|
|
31
|
-
return
|
|
32
|
-
if (envelope.kind === "document_comment") {
|
|
33
|
-
return typeof envelope.target === "string" && envelope.target ? envelope.target : null;
|
|
124
|
+
try {
|
|
125
|
+
return targetKeyOfInboxEnvelope(envelope);
|
|
34
126
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
? `dm:@${String(envelope.parent_channel_name)}`
|
|
38
|
-
: `#${String(envelope.parent_channel_name)}`;
|
|
39
|
-
return `${base}:${String(envelope.channel_name || "").slice(0, 8)}`;
|
|
127
|
+
catch {
|
|
128
|
+
return null;
|
|
40
129
|
}
|
|
41
|
-
return envelope.channel_type === "dm"
|
|
42
|
-
? `dm:@${String(envelope.channel_name)}`
|
|
43
|
-
: `#${String(envelope.channel_name)}`;
|
|
44
130
|
}
|
|
45
131
|
/** Project persisted inbox envelopes into the exact agentApi events response data shape. */
|
|
46
132
|
export function projectInboxEvents(envelopes) {
|
|
@@ -49,7 +135,7 @@ export function projectInboxEvents(envelopes) {
|
|
|
49
135
|
events: envelopes,
|
|
50
136
|
last_seen_msgId: last ? (last.message_id || null) : null,
|
|
51
137
|
last_seen_seq: last ? (last.seq ?? null) : null,
|
|
52
|
-
reply_target:
|
|
138
|
+
reply_target: last ? targetKeyOfInboxEnvelope(last) : null,
|
|
53
139
|
pending_notice_ids: [],
|
|
54
140
|
wake_reason: null,
|
|
55
141
|
has_more: false,
|
|
@@ -57,9 +143,12 @@ export function projectInboxEvents(envelopes) {
|
|
|
57
143
|
}
|
|
58
144
|
/** Bounded, content-light and deterministic projection for the read-only check command. */
|
|
59
145
|
export function projectInboxCheck(envelopes, onlyTarget) {
|
|
146
|
+
if (onlyTarget && !isCanonicalInboxTarget(onlyTarget)) {
|
|
147
|
+
throw new Error(`Invalid canonical Inbox check target ${JSON.stringify(onlyTarget)}`);
|
|
148
|
+
}
|
|
60
149
|
const byTarget = new Map();
|
|
61
150
|
for (const envelope of envelopes) {
|
|
62
|
-
const target =
|
|
151
|
+
const target = targetKeyOfInboxEnvelope(envelope);
|
|
63
152
|
if (onlyTarget && onlyTarget !== target)
|
|
64
153
|
continue;
|
|
65
154
|
const rows = byTarget.get(target) ?? [];
|
|
@@ -11,6 +11,7 @@ exports.resolveDocumentCommentContext = resolveDocumentCommentContext;
|
|
|
11
11
|
exports.projectDocumentCommentEnvelope = projectDocumentCommentEnvelope;
|
|
12
12
|
const node_crypto_1 = require("node:crypto");
|
|
13
13
|
exports.DOCUMENT_COMMENT_EVENT = "drive.notice.comment_add_v1";
|
|
14
|
+
const DOCUMENT_COMMENT_TARGET_PATTERN = /^document-comment:(doc|docx|sheet|file):([A-Za-z0-9_-]+):([A-Za-z0-9_-]+):(in-thread|top-level)$/;
|
|
14
15
|
function record(value) {
|
|
15
16
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
16
17
|
}
|
|
@@ -39,7 +40,7 @@ function documentCommentTarget(target, commentId, topLevel = false) {
|
|
|
39
40
|
return `document-comment:${target.fileType}:${target.fileToken}:${commentId}:${topLevel ? "top-level" : "in-thread"}`;
|
|
40
41
|
}
|
|
41
42
|
function parseDocumentCommentTarget(value) {
|
|
42
|
-
const match =
|
|
43
|
+
const match = DOCUMENT_COMMENT_TARGET_PATTERN.exec(value);
|
|
43
44
|
return match ? {
|
|
44
45
|
fileType: match[1], fileToken: match[2], commentId: match[3], topLevel: match[4] === "top-level",
|
|
45
46
|
} : null;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
export const DOCUMENT_COMMENT_EVENT = "drive.notice.comment_add_v1";
|
|
3
|
+
const DOCUMENT_COMMENT_TARGET_PATTERN = /^document-comment:(doc|docx|sheet|file):([A-Za-z0-9_-]+):([A-Za-z0-9_-]+):(in-thread|top-level)$/;
|
|
3
4
|
function record(value) {
|
|
4
5
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
5
6
|
}
|
|
@@ -28,7 +29,7 @@ export function documentCommentTarget(target, commentId, topLevel = false) {
|
|
|
28
29
|
return `document-comment:${target.fileType}:${target.fileToken}:${commentId}:${topLevel ? "top-level" : "in-thread"}`;
|
|
29
30
|
}
|
|
30
31
|
export function parseDocumentCommentTarget(value) {
|
|
31
|
-
const match =
|
|
32
|
+
const match = DOCUMENT_COMMENT_TARGET_PATTERN.exec(value);
|
|
32
33
|
return match ? {
|
|
33
34
|
fileType: match[1], fileToken: match[2], commentId: match[3], topLevel: match[4] === "top-level",
|
|
34
35
|
} : null;
|
|
@@ -10,6 +10,7 @@ exports.shouldPreventiveReconnect = shouldPreventiveReconnect;
|
|
|
10
10
|
exports.countWakeEnvelopes = countWakeEnvelopes;
|
|
11
11
|
exports.projectActivityStatus = projectActivityStatus;
|
|
12
12
|
exports.projectSessionStatus = projectSessionStatus;
|
|
13
|
+
const inbox_projection_js_1 = require("../agent/inbox-projection.cjs");
|
|
13
14
|
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
14
15
|
const message_policy_js_1 = require("./message-policy.cjs");
|
|
15
16
|
function asRecord(value) {
|
|
@@ -337,7 +338,8 @@ class HostEnvelopeProjector {
|
|
|
337
338
|
: `本条存量提醒缺少可用的飞书 message_id/chat_id,无法安全推断原会话;请先用 ${this.larkCommand("im +chat-search")} 确认目标,禁止猜测发送`,
|
|
338
339
|
`这是你之前用 ${this.agentCommand("reminder schedule")} 设置的提醒,请按标题执行相应动作。管理: ${this.agentCommand("reminder list")} / ${this.agentCommand("reminder snooze")} / ${this.agentCommand("reminder cancel")}`,
|
|
339
340
|
].filter((line) => Boolean(line));
|
|
340
|
-
|
|
341
|
+
const envelope = {
|
|
342
|
+
kind: "reminder",
|
|
341
343
|
message_id: `rem_${reminder.reminderId.slice(0, 16)}_${seq}`,
|
|
342
344
|
seq,
|
|
343
345
|
sender_name: "定时提醒",
|
|
@@ -349,10 +351,12 @@ class HostEnvelopeProjector {
|
|
|
349
351
|
thread_id: null,
|
|
350
352
|
wake: true,
|
|
351
353
|
};
|
|
354
|
+
return { ...envelope, target: inbox_projection_js_1.RUNTIME_REMINDER_TARGET };
|
|
352
355
|
}
|
|
353
356
|
createRedeliveryEnvelope(agentId, wakeCount) {
|
|
354
357
|
const seq = this.nextSequence(agentId);
|
|
355
|
-
|
|
358
|
+
const envelope = {
|
|
359
|
+
kind: "redelivery",
|
|
356
360
|
message_id: `redeliver_${this.randomHex(6)}`,
|
|
357
361
|
seq,
|
|
358
362
|
sender_name: "系统",
|
|
@@ -363,6 +367,7 @@ class HostEnvelopeProjector {
|
|
|
363
367
|
timestamp: this.now().toISOString(),
|
|
364
368
|
thread_id: null,
|
|
365
369
|
};
|
|
370
|
+
return { ...envelope, target: inbox_projection_js_1.RUNTIME_REDELIVERY_TARGET };
|
|
366
371
|
}
|
|
367
372
|
}
|
|
368
373
|
exports.HostEnvelopeProjector = HostEnvelopeProjector;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RUNTIME_REDELIVERY_TARGET, RUNTIME_REMINDER_TARGET } from "../agent/inbox-projection.mjs";
|
|
1
2
|
import crypto from "node:crypto";
|
|
2
3
|
import { createMessageEnvelope, targetAliases, targetFor, } from "./message-policy.mjs";
|
|
3
4
|
function asRecord(value) {
|
|
@@ -323,7 +324,8 @@ export class HostEnvelopeProjector {
|
|
|
323
324
|
: `本条存量提醒缺少可用的飞书 message_id/chat_id,无法安全推断原会话;请先用 ${this.larkCommand("im +chat-search")} 确认目标,禁止猜测发送`,
|
|
324
325
|
`这是你之前用 ${this.agentCommand("reminder schedule")} 设置的提醒,请按标题执行相应动作。管理: ${this.agentCommand("reminder list")} / ${this.agentCommand("reminder snooze")} / ${this.agentCommand("reminder cancel")}`,
|
|
325
326
|
].filter((line) => Boolean(line));
|
|
326
|
-
|
|
327
|
+
const envelope = {
|
|
328
|
+
kind: "reminder",
|
|
327
329
|
message_id: `rem_${reminder.reminderId.slice(0, 16)}_${seq}`,
|
|
328
330
|
seq,
|
|
329
331
|
sender_name: "定时提醒",
|
|
@@ -335,10 +337,12 @@ export class HostEnvelopeProjector {
|
|
|
335
337
|
thread_id: null,
|
|
336
338
|
wake: true,
|
|
337
339
|
};
|
|
340
|
+
return { ...envelope, target: RUNTIME_REMINDER_TARGET };
|
|
338
341
|
}
|
|
339
342
|
createRedeliveryEnvelope(agentId, wakeCount) {
|
|
340
343
|
const seq = this.nextSequence(agentId);
|
|
341
|
-
|
|
344
|
+
const envelope = {
|
|
345
|
+
kind: "redelivery",
|
|
342
346
|
message_id: `redeliver_${this.randomHex(6)}`,
|
|
343
347
|
seq,
|
|
344
348
|
sender_name: "系统",
|
|
@@ -349,6 +353,7 @@ export class HostEnvelopeProjector {
|
|
|
349
353
|
timestamp: this.now().toISOString(),
|
|
350
354
|
thread_id: null,
|
|
351
355
|
};
|
|
356
|
+
return { ...envelope, target: RUNTIME_REDELIVERY_TARGET };
|
|
352
357
|
}
|
|
353
358
|
}
|
|
354
359
|
export function countWakeEnvelopes(lines) {
|
|
@@ -200,6 +200,16 @@ function createHostShell({ env = process.env, runtimeHost, channelPackage, event
|
|
|
200
200
|
return store;
|
|
201
201
|
};
|
|
202
202
|
const hostState = new host_business_state_js_1.HostStateProjection(stateStore, log);
|
|
203
|
+
const recordInboundDeliveryFailure = (agent, code) => {
|
|
204
|
+
const at = new Date().toISOString();
|
|
205
|
+
const reason = "Inbound Runtime delivery failed non-retryably; durable Inbox/ledger state is retained for recovery.";
|
|
206
|
+
const nextAction = "Inspect the delivery/status error, correct the Runtime or canonical Inbox state, then restart to replay safely.";
|
|
207
|
+
hostState.updateStatus(agent, {
|
|
208
|
+
inboundDeliveryHealth: { state: "error", code, at, reason, nextAction },
|
|
209
|
+
runtimeReadiness: { runtime: agent.runtime, state: "incompatible", reason, nextAction },
|
|
210
|
+
});
|
|
211
|
+
hostState.recordStatusError(agent, `${reason} ${nextAction} code=${code}`);
|
|
212
|
+
};
|
|
203
213
|
const agentStates = new Map();
|
|
204
214
|
const saveAgentState = (record) => {
|
|
205
215
|
try {
|
|
@@ -335,6 +345,7 @@ function createHostShell({ env = process.env, runtimeHost, channelPackage, event
|
|
|
335
345
|
return;
|
|
336
346
|
}
|
|
337
347
|
const telemetryMessageId = String(event.message_id || event.event_id || eventKey);
|
|
348
|
+
let canonicalInboxDurable = false;
|
|
338
349
|
if (wake)
|
|
339
350
|
telemetry?.beginMessage(agent.agentId, telemetryMessageId);
|
|
340
351
|
if (event.event_id)
|
|
@@ -345,58 +356,76 @@ function createHostShell({ env = process.env, runtimeHost, channelPackage, event
|
|
|
345
356
|
senderIdentity.ensureChatNames(agent, event.chat_id, 3_000),
|
|
346
357
|
event._sender_is_bot ? Promise.resolve(null) : senderIdentity.ensureSenderSignature(agent, event.sender_id, 3_000),
|
|
347
358
|
]);
|
|
348
|
-
const
|
|
349
|
-
|
|
359
|
+
const projected = envelopeProjector.projectInbound(agent, event, { anchorReply: wake, names, signature });
|
|
360
|
+
projected.target = (0, inbox_projection_js_1.targetKeyOfInboxEnvelope)({ ...projected, chat_id: event.chat_id, thread_id: event.thread_id });
|
|
350
361
|
if (wake)
|
|
351
|
-
|
|
352
|
-
const
|
|
362
|
+
projected.wake = true;
|
|
363
|
+
const candidate = (0, inbox_projection_js_1.projectInboxEnvelope)(projected, {
|
|
353
364
|
chat_id: event.chat_id,
|
|
354
365
|
thread_id: event.thread_id,
|
|
355
366
|
...(event.create_time !== undefined ? { create_time: String(event.create_time) } : {}),
|
|
356
367
|
sender_id: event.sender_id,
|
|
357
|
-
content: String(
|
|
368
|
+
content: String(projected.content ?? event.content ?? ""),
|
|
358
369
|
});
|
|
370
|
+
let append;
|
|
359
371
|
try {
|
|
360
|
-
stateStore(agent).
|
|
372
|
+
append = stateStore(agent).appendCanonicalInboxOnce(candidate);
|
|
361
373
|
}
|
|
362
374
|
catch (error) {
|
|
363
375
|
throw new Error(`inbox 写失败: ${errorMessage(error)}`);
|
|
364
376
|
}
|
|
365
|
-
|
|
366
|
-
//
|
|
377
|
+
canonicalInboxDurable = true;
|
|
378
|
+
// An event becomes permanently transport-seen only after the canonical
|
|
379
|
+
// append/dedupe decision is durable. Agent model-seen state is untouched.
|
|
367
380
|
if (event.event_id)
|
|
368
381
|
seenEventIds.add(eventKey);
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
382
|
+
if (append.status === "duplicate_consumed")
|
|
383
|
+
return null;
|
|
384
|
+
const inboxEnvelope = append.envelope;
|
|
385
|
+
if (append.status === "appended")
|
|
386
|
+
hostState.appendConversation(agent, {
|
|
387
|
+
direction: "in", from: inboxEnvelope.sender_name, senderType: inboxEnvelope.sender_type,
|
|
388
|
+
target: String(inboxEnvelope.target || (0, message_policy_js_1.targetFor)(event).target), wake, text: event.content, messageId: inboxEnvelope.message_id,
|
|
389
|
+
at: inboxEnvelope.timestamp || new Date().toISOString(),
|
|
390
|
+
});
|
|
391
|
+
return inboxEnvelope;
|
|
375
392
|
};
|
|
376
|
-
const
|
|
393
|
+
const inboxEnvelope = wake && telemetry
|
|
377
394
|
? await telemetry.phase(telemetryMessageId, "feishu.receive", api_1.SpanKind.CONSUMER, receive)
|
|
378
395
|
: await receive();
|
|
379
|
-
if (!wake)
|
|
396
|
+
if (!wake || !inboxEnvelope)
|
|
380
397
|
return;
|
|
381
|
-
const receipt = await runtimeHost.deliver(agent.agentId,
|
|
382
|
-
if (receipt.status === "
|
|
398
|
+
const receipt = await runtimeHost.deliver(agent.agentId, inboxEnvelope);
|
|
399
|
+
if (receipt.status === "error") {
|
|
400
|
+
recordInboundDeliveryFailure(agent, "non_retryable_receipt");
|
|
383
401
|
hostState.appendStatusLog(agent, "deliverLog", {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
}, 30);
|
|
389
|
-
if (envelope.sender_type === "human" || envelope.sender_type === "agent")
|
|
390
|
-
processingEyes.add(agent, String(envelope.message_id || ""));
|
|
391
|
-
if (receipt.status === "deferred")
|
|
392
|
-
log(`Runtime 暂缓投递,消息保留在 inbox seq=${envelope.seq}: ${receipt.reason}`);
|
|
402
|
+
at: new Date().toISOString(), status: "error",
|
|
403
|
+
reason: "non-retryable Runtime delivery error; durable recovery state retained",
|
|
404
|
+
}, 80);
|
|
405
|
+
return;
|
|
393
406
|
}
|
|
407
|
+
hostState.appendStatusLog(agent, "deliverLog", {
|
|
408
|
+
from: inboxEnvelope.sender_name,
|
|
409
|
+
target: String(inboxEnvelope.target || (0, message_policy_js_1.targetFor)(event).target),
|
|
410
|
+
excerpt: (0, host_business_state_js_1.safeConversationExcerpt)(event.content, 180),
|
|
411
|
+
at: new Date().toISOString(),
|
|
412
|
+
}, 30);
|
|
413
|
+
if (inboxEnvelope.sender_type === "human" || inboxEnvelope.sender_type === "agent")
|
|
414
|
+
processingEyes.add(agent, String(inboxEnvelope.message_id || ""));
|
|
415
|
+
if (receipt.status === "deferred")
|
|
416
|
+
log(`Runtime 暂缓投递,消息保留在 inbox seq=${inboxEnvelope.seq}: ${receipt.reason}`);
|
|
394
417
|
}
|
|
395
418
|
catch (error) {
|
|
396
419
|
if (wake)
|
|
397
420
|
telemetry?.delivery(agent.agentId, telemetryMessageId, "error");
|
|
398
|
-
|
|
399
|
-
|
|
421
|
+
if (canonicalInboxDurable) {
|
|
422
|
+
log(`onFeishuMessage Runtime delivery failed agent=${agent.name}; canonical Inbox retained`);
|
|
423
|
+
recordInboundDeliveryFailure(agent, "runtime_delivery_exception");
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
log(`onFeishuMessage 异常 agent=${agent.name}: ${error instanceof Error ? error.stack || error.message : String(error)}`);
|
|
427
|
+
hostState.recordStatusError(agent, `onFeishuMessage: ${errorMessage(error)}`);
|
|
428
|
+
}
|
|
400
429
|
}
|
|
401
430
|
finally {
|
|
402
431
|
if (event.event_id)
|
|
@@ -1241,9 +1270,14 @@ function createHostShell({ env = process.env, runtimeHost, channelPackage, event
|
|
|
1241
1270
|
return;
|
|
1242
1271
|
}
|
|
1243
1272
|
if (message.type === "delivery") {
|
|
1273
|
+
if (message.status === "error")
|
|
1274
|
+
recordInboundDeliveryFailure(agent, "runtime_delivery_event");
|
|
1244
1275
|
hostState.appendStatusLog(agent, "deliverLog", {
|
|
1245
1276
|
at: new Date().toISOString(), deliveryId: message.deliveryId, messageId: message.messageId,
|
|
1246
|
-
status: message.status,
|
|
1277
|
+
status: message.status,
|
|
1278
|
+
...(message.status === "error"
|
|
1279
|
+
? { reason: "non-retryable Runtime delivery error; durable recovery state retained" }
|
|
1280
|
+
: (message.reason ? { reason: (0, host_business_state_js_1.safeConversationExcerpt)(message.reason, 120) } : {})),
|
|
1247
1281
|
}, 80);
|
|
1248
1282
|
return;
|
|
1249
1283
|
}
|
|
@@ -1266,7 +1300,8 @@ function createHostShell({ env = process.env, runtimeHost, channelPackage, event
|
|
|
1266
1300
|
hostState.recordStatusError(agent, `auth: ${readiness.reason}; ${readiness.nextAction}`);
|
|
1267
1301
|
}
|
|
1268
1302
|
else {
|
|
1269
|
-
|
|
1303
|
+
const category = message.event.errorCategory || "provider";
|
|
1304
|
+
hostState.recordStatusError(agent, `${category}: Runtime input failed ${message.event.retryable ? "retryably" : "non-retryably"}; inspect delivery health and Runtime/provider configuration`);
|
|
1270
1305
|
}
|
|
1271
1306
|
}
|
|
1272
1307
|
};
|