@nextclaw/channel-plugin-feishu 0.2.29-beta.0 → 0.2.29-beta.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.
Files changed (114) hide show
  1. package/dist/index.d.ts +23 -0
  2. package/dist/index.js +45 -0
  3. package/dist/src/accounts.js +141 -0
  4. package/dist/src/app-scope-checker.js +36 -0
  5. package/dist/src/async.js +34 -0
  6. package/dist/src/auth-errors.js +72 -0
  7. package/dist/src/bitable.js +495 -0
  8. package/dist/src/bot.d.ts +35 -0
  9. package/dist/src/bot.js +941 -0
  10. package/dist/src/calendar-calendar.js +54 -0
  11. package/dist/src/calendar-event-attendee.js +98 -0
  12. package/dist/src/calendar-event.js +193 -0
  13. package/dist/src/calendar-freebusy.js +40 -0
  14. package/dist/src/calendar-shared.js +23 -0
  15. package/dist/src/calendar.js +16 -0
  16. package/dist/src/card-action.js +49 -0
  17. package/dist/src/channel.d.ts +7 -0
  18. package/dist/src/channel.js +413 -0
  19. package/dist/src/chat-schema.js +25 -0
  20. package/dist/src/chat.js +87 -0
  21. package/dist/src/client.d.ts +16 -0
  22. package/dist/src/client.js +112 -0
  23. package/dist/src/config-schema.d.ts +357 -0
  24. package/dist/src/dedup.js +126 -0
  25. package/dist/src/device-flow.js +109 -0
  26. package/dist/src/directory.js +101 -0
  27. package/dist/src/doc-schema.js +148 -0
  28. package/dist/src/docx-batch-insert.js +104 -0
  29. package/dist/src/docx-color-text.js +80 -0
  30. package/dist/src/docx-table-ops.js +197 -0
  31. package/dist/src/docx.js +858 -0
  32. package/dist/src/domains.js +14 -0
  33. package/dist/src/drive-schema.js +41 -0
  34. package/dist/src/drive.js +126 -0
  35. package/dist/src/dynamic-agent.js +93 -0
  36. package/dist/src/external-keys.js +13 -0
  37. package/dist/src/feishu-fetch.js +12 -0
  38. package/dist/src/identity.js +92 -0
  39. package/dist/src/lark-ticket.js +11 -0
  40. package/dist/src/media.d.ts +75 -0
  41. package/dist/src/media.js +304 -0
  42. package/dist/src/mention.d.ts +52 -0
  43. package/dist/src/mention.js +82 -0
  44. package/dist/src/monitor.account.d.ts +1 -0
  45. package/dist/src/monitor.account.js +393 -0
  46. package/dist/src/monitor.d.ts +11 -0
  47. package/dist/src/monitor.js +58 -0
  48. package/dist/src/monitor.startup.js +24 -0
  49. package/dist/src/monitor.state.d.ts +1 -0
  50. package/dist/src/monitor.state.js +80 -0
  51. package/dist/src/monitor.transport.js +167 -0
  52. package/dist/src/nextclaw-sdk/account-id.js +15 -0
  53. package/dist/src/nextclaw-sdk/core-channel.js +150 -0
  54. package/dist/src/nextclaw-sdk/core-pairing.js +151 -0
  55. package/dist/src/nextclaw-sdk/dedupe.js +164 -0
  56. package/dist/src/nextclaw-sdk/feishu.d.ts +1 -0
  57. package/dist/src/nextclaw-sdk/feishu.js +14 -0
  58. package/dist/src/nextclaw-sdk/history.js +69 -0
  59. package/dist/src/nextclaw-sdk/network-body.js +180 -0
  60. package/dist/src/nextclaw-sdk/network-fetch.js +63 -0
  61. package/dist/src/nextclaw-sdk/network-webhook.js +126 -0
  62. package/dist/src/nextclaw-sdk/network.js +4 -0
  63. package/dist/src/nextclaw-sdk/runtime-store.js +21 -0
  64. package/dist/src/nextclaw-sdk/secrets-config.js +65 -0
  65. package/dist/src/nextclaw-sdk/secrets-core.d.ts +1 -0
  66. package/dist/src/nextclaw-sdk/secrets-core.js +68 -0
  67. package/dist/src/nextclaw-sdk/secrets-prompt.js +193 -0
  68. package/dist/src/nextclaw-sdk/secrets.d.ts +1 -0
  69. package/dist/src/nextclaw-sdk/secrets.js +4 -0
  70. package/dist/src/nextclaw-sdk/types.d.ts +242 -0
  71. package/dist/src/oauth.js +171 -0
  72. package/dist/src/onboarding.js +381 -0
  73. package/dist/src/outbound.js +150 -0
  74. package/dist/src/perm-schema.js +49 -0
  75. package/dist/src/perm.js +90 -0
  76. package/dist/src/policy.js +61 -0
  77. package/dist/src/post.js +160 -0
  78. package/dist/src/probe.d.ts +11 -0
  79. package/dist/src/probe.js +85 -0
  80. package/dist/src/raw-request.js +24 -0
  81. package/dist/src/reactions.d.ts +67 -0
  82. package/dist/src/reactions.js +91 -0
  83. package/dist/src/reply-dispatcher.js +250 -0
  84. package/dist/src/runtime.js +5 -0
  85. package/dist/src/secret-input.js +3 -0
  86. package/dist/src/send-result.js +12 -0
  87. package/dist/src/send-target.js +22 -0
  88. package/dist/src/send.d.ts +51 -0
  89. package/dist/src/send.js +265 -0
  90. package/dist/src/sheets-shared.js +193 -0
  91. package/dist/src/sheets.js +95 -0
  92. package/dist/src/streaming-card.js +263 -0
  93. package/dist/src/targets.js +39 -0
  94. package/dist/src/task-comment.js +76 -0
  95. package/dist/src/task-shared.js +13 -0
  96. package/dist/src/task-subtask.js +79 -0
  97. package/dist/src/task-task.js +144 -0
  98. package/dist/src/task-tasklist.js +136 -0
  99. package/dist/src/task.js +16 -0
  100. package/dist/src/token-store.js +154 -0
  101. package/dist/src/tool-account.js +65 -0
  102. package/dist/src/tool-result.js +18 -0
  103. package/dist/src/tool-scopes.js +62 -0
  104. package/dist/src/tools-config.js +30 -0
  105. package/dist/src/types.d.ts +43 -0
  106. package/dist/src/typing.js +145 -0
  107. package/dist/src/uat-client.js +102 -0
  108. package/dist/src/user-tool-client.js +132 -0
  109. package/dist/src/user-tool-helpers.js +110 -0
  110. package/dist/src/user-tool-result.js +10 -0
  111. package/dist/src/wiki-schema.js +45 -0
  112. package/dist/src/wiki.js +144 -0
  113. package/package.json +8 -4
  114. package/index.ts +0 -75
@@ -0,0 +1,69 @@
1
+ //#region src/nextclaw-sdk/history.ts
2
+ const HISTORY_CONTEXT_MARKER = "[Chat messages since your last reply - for context]";
3
+ const CURRENT_MESSAGE_MARKER = "[Current message]";
4
+ const MAX_HISTORY_KEYS = 1e3;
5
+ function evictOldHistoryKeys(historyMap, maxKeys = MAX_HISTORY_KEYS) {
6
+ if (historyMap.size <= maxKeys) return;
7
+ const keysToDelete = historyMap.size - maxKeys;
8
+ const iterator = historyMap.keys();
9
+ for (let index = 0; index < keysToDelete; index += 1) {
10
+ const key = iterator.next().value;
11
+ if (key !== void 0) historyMap.delete(key);
12
+ }
13
+ }
14
+ function buildHistoryContext(params) {
15
+ const lineBreak = params.lineBreak ?? "\n";
16
+ if (!params.historyText.trim()) return params.currentMessage;
17
+ return [
18
+ HISTORY_CONTEXT_MARKER,
19
+ params.historyText,
20
+ "",
21
+ CURRENT_MESSAGE_MARKER,
22
+ params.currentMessage
23
+ ].join(lineBreak);
24
+ }
25
+ function appendHistoryEntry(params) {
26
+ if (params.limit <= 0) return [];
27
+ const history = params.historyMap.get(params.historyKey) ?? [];
28
+ history.push(params.entry);
29
+ while (history.length > params.limit) history.shift();
30
+ if (params.historyMap.has(params.historyKey)) params.historyMap.delete(params.historyKey);
31
+ params.historyMap.set(params.historyKey, history);
32
+ evictOldHistoryKeys(params.historyMap);
33
+ return history;
34
+ }
35
+ function buildHistoryContextFromEntries(params) {
36
+ const lineBreak = params.lineBreak ?? "\n";
37
+ const entries = params.excludeLast === false ? params.entries : params.entries.slice(0, -1);
38
+ if (entries.length === 0) return params.currentMessage;
39
+ return buildHistoryContext({
40
+ historyText: entries.map(params.formatEntry).join(lineBreak),
41
+ currentMessage: params.currentMessage,
42
+ lineBreak
43
+ });
44
+ }
45
+ function recordPendingHistoryEntryIfEnabled(params) {
46
+ if (!params.entry || params.limit <= 0) return [];
47
+ return appendHistoryEntry({
48
+ historyMap: params.historyMap,
49
+ historyKey: params.historyKey,
50
+ entry: params.entry,
51
+ limit: params.limit
52
+ });
53
+ }
54
+ function buildPendingHistoryContextFromMap(params) {
55
+ if (params.limit <= 0) return params.currentMessage;
56
+ return buildHistoryContextFromEntries({
57
+ entries: params.historyMap.get(params.historyKey) ?? [],
58
+ currentMessage: params.currentMessage,
59
+ formatEntry: params.formatEntry,
60
+ lineBreak: params.lineBreak,
61
+ excludeLast: false
62
+ });
63
+ }
64
+ function clearHistoryEntriesIfEnabled(params) {
65
+ if (params.limit <= 0) return;
66
+ params.historyMap.set(params.historyKey, []);
67
+ }
68
+ //#endregion
69
+ export { buildPendingHistoryContextFromMap, clearHistoryEntriesIfEnabled, recordPendingHistoryEntryIfEnabled };
@@ -0,0 +1,180 @@
1
+ //#region src/nextclaw-sdk/network-body.ts
2
+ var RequestBodyLimitError = class extends Error {
3
+ code;
4
+ statusCode;
5
+ constructor(code) {
6
+ super(code);
7
+ this.name = "RequestBodyLimitError";
8
+ this.code = code;
9
+ this.statusCode = code === "PAYLOAD_TOO_LARGE" ? 413 : code === "REQUEST_BODY_TIMEOUT" ? 408 : 400;
10
+ }
11
+ };
12
+ function requestBodyErrorToText(code) {
13
+ switch (code) {
14
+ case "PAYLOAD_TOO_LARGE": return "Payload too large";
15
+ case "REQUEST_BODY_TIMEOUT": return "Request body timeout";
16
+ default: return "Connection closed";
17
+ }
18
+ }
19
+ function parseContentLengthHeader(req) {
20
+ const header = req.headers["content-length"];
21
+ const raw = Array.isArray(header) ? header[0] : header;
22
+ if (typeof raw !== "string") return null;
23
+ const parsed = Number.parseInt(raw, 10);
24
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
25
+ }
26
+ async function readRequestBodyWithLimit(req, options) {
27
+ const maxBytes = Math.max(1, Math.floor(options.maxBytes));
28
+ const timeoutMs = typeof options.timeoutMs === "number" && options.timeoutMs > 0 ? Math.floor(options.timeoutMs) : 3e4;
29
+ const encoding = options.encoding ?? "utf-8";
30
+ const declaredLength = parseContentLengthHeader(req);
31
+ if (declaredLength !== null && declaredLength > maxBytes) throw new RequestBodyLimitError("PAYLOAD_TOO_LARGE");
32
+ return await new Promise((resolve, reject) => {
33
+ let done = false;
34
+ let ended = false;
35
+ let totalBytes = 0;
36
+ const chunks = [];
37
+ const finish = (cb) => {
38
+ if (done) return;
39
+ done = true;
40
+ req.removeListener("data", onData);
41
+ req.removeListener("end", onEnd);
42
+ req.removeListener("error", onError);
43
+ req.removeListener("close", onClose);
44
+ clearTimeout(timer);
45
+ cb();
46
+ };
47
+ const fail = (error) => finish(() => reject(error));
48
+ const onData = (chunk) => {
49
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
50
+ totalBytes += buffer.length;
51
+ if (totalBytes > maxBytes) {
52
+ if (!req.destroyed) req.destroy();
53
+ fail(new RequestBodyLimitError("PAYLOAD_TOO_LARGE"));
54
+ return;
55
+ }
56
+ chunks.push(buffer);
57
+ };
58
+ const onEnd = () => {
59
+ ended = true;
60
+ finish(() => resolve(Buffer.concat(chunks).toString(encoding)));
61
+ };
62
+ const onError = (error) => fail(error);
63
+ const onClose = () => {
64
+ if (!done && !ended) fail(new RequestBodyLimitError("CONNECTION_CLOSED"));
65
+ };
66
+ const timer = setTimeout(() => {
67
+ if (!req.destroyed) req.destroy();
68
+ fail(new RequestBodyLimitError("REQUEST_BODY_TIMEOUT"));
69
+ }, timeoutMs);
70
+ req.on("data", onData);
71
+ req.on("end", onEnd);
72
+ req.on("error", onError);
73
+ req.on("close", onClose);
74
+ });
75
+ }
76
+ async function readJsonBodyWithLimit(req, options) {
77
+ try {
78
+ const trimmed = (await readRequestBodyWithLimit(req, options)).trim();
79
+ if (!trimmed) {
80
+ if (options.emptyObjectOnEmpty === false) return {
81
+ ok: false,
82
+ code: "INVALID_JSON",
83
+ error: "empty payload"
84
+ };
85
+ return {
86
+ ok: true,
87
+ value: {}
88
+ };
89
+ }
90
+ try {
91
+ return {
92
+ ok: true,
93
+ value: JSON.parse(trimmed)
94
+ };
95
+ } catch (error) {
96
+ return {
97
+ ok: false,
98
+ code: "INVALID_JSON",
99
+ error: error instanceof Error ? error.message : String(error)
100
+ };
101
+ }
102
+ } catch (error) {
103
+ if (error instanceof RequestBodyLimitError) return {
104
+ ok: false,
105
+ code: error.code,
106
+ error: requestBodyErrorToText(error.code)
107
+ };
108
+ return {
109
+ ok: false,
110
+ code: "INVALID_JSON",
111
+ error: error instanceof Error ? error.message : String(error)
112
+ };
113
+ }
114
+ }
115
+ function installRequestBodyLimitGuard(req, res, options) {
116
+ const maxBytes = Math.max(1, Math.floor(options.maxBytes));
117
+ const timeoutMs = typeof options.timeoutMs === "number" && options.timeoutMs > 0 ? Math.floor(options.timeoutMs) : 3e4;
118
+ const responseFormat = options.responseFormat ?? "json";
119
+ let tripped = false;
120
+ let code = null;
121
+ let done = false;
122
+ let ended = false;
123
+ let totalBytes = 0;
124
+ const finish = () => {
125
+ if (done) return;
126
+ done = true;
127
+ req.removeListener("data", onData);
128
+ req.removeListener("end", onEnd);
129
+ req.removeListener("close", onClose);
130
+ req.removeListener("error", onError);
131
+ clearTimeout(timer);
132
+ };
133
+ const respond = (error) => {
134
+ if (res.headersSent) return;
135
+ const text = requestBodyErrorToText(error.code);
136
+ res.statusCode = error.statusCode;
137
+ if (responseFormat === "text") {
138
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
139
+ res.end(text);
140
+ return;
141
+ }
142
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
143
+ res.end(JSON.stringify({ error: text }));
144
+ };
145
+ const trip = (error) => {
146
+ if (tripped) return;
147
+ tripped = true;
148
+ code = error.code;
149
+ finish();
150
+ respond(error);
151
+ if (!req.destroyed) req.destroy();
152
+ };
153
+ const onData = (chunk) => {
154
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
155
+ totalBytes += buffer.length;
156
+ if (totalBytes > maxBytes) trip(new RequestBodyLimitError("PAYLOAD_TOO_LARGE"));
157
+ };
158
+ const onEnd = () => {
159
+ ended = true;
160
+ finish();
161
+ };
162
+ const onClose = () => {
163
+ if (!ended) finish();
164
+ };
165
+ const onError = () => finish();
166
+ const timer = setTimeout(() => trip(new RequestBodyLimitError("REQUEST_BODY_TIMEOUT")), timeoutMs);
167
+ req.on("data", onData);
168
+ req.on("end", onEnd);
169
+ req.on("close", onClose);
170
+ req.on("error", onError);
171
+ const declaredLength = parseContentLengthHeader(req);
172
+ if (declaredLength !== null && declaredLength > maxBytes) trip(new RequestBodyLimitError("PAYLOAD_TOO_LARGE"));
173
+ return {
174
+ dispose: finish,
175
+ isTripped: () => tripped,
176
+ code: () => code
177
+ };
178
+ }
179
+ //#endregion
180
+ export { installRequestBodyLimitGuard, readJsonBodyWithLimit };
@@ -0,0 +1,63 @@
1
+ import path from "node:path";
2
+ import "node:crypto";
3
+ import os from "node:os";
4
+ import { mkdtemp, rm } from "node:fs/promises";
5
+ //#region src/nextclaw-sdk/network-fetch.ts
6
+ function sanitizePrefix(prefix) {
7
+ return prefix.replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "tmp";
8
+ }
9
+ function sanitizeFileName(fileName) {
10
+ return path.basename(fileName).replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "download.bin";
11
+ }
12
+ function resolveTempRoot(tmpDir) {
13
+ return tmpDir ?? process.env.NEXTCLAW_TMP_DIR?.trim() ?? os.tmpdir();
14
+ }
15
+ async function withTempDownloadPath(params, fn) {
16
+ const root = resolveTempRoot(params.tmpDir);
17
+ const dir = await mkdtemp(path.join(root, `${sanitizePrefix(params.prefix)}-`));
18
+ const tempPath = path.join(dir, sanitizeFileName(params.fileName ?? "download.bin"));
19
+ try {
20
+ return await fn(tempPath);
21
+ } finally {
22
+ try {
23
+ await rm(dir, {
24
+ recursive: true,
25
+ force: true
26
+ });
27
+ } catch {}
28
+ }
29
+ }
30
+ async function fetchWithSsrFGuard(params) {
31
+ const fetcher = params.fetchImpl ?? globalThis.fetch;
32
+ if (!fetcher) throw new Error("fetch is not available");
33
+ const parsed = new URL(params.url);
34
+ if (!["http:", "https:"].includes(parsed.protocol)) throw new Error("Invalid URL: must be http or https");
35
+ const allowedHostnames = params.policy?.allowedHostnames?.map((entry) => entry.trim()).filter(Boolean) ?? [];
36
+ if (allowedHostnames.length > 0 && !allowedHostnames.includes(parsed.hostname)) throw new Error(`${params.auditContext ?? "guarded-fetch"} blocked hostname "${parsed.hostname}"`);
37
+ const controller = new AbortController();
38
+ const timeoutId = params.timeoutMs && params.timeoutMs > 0 ? setTimeout(() => controller.abort(), params.timeoutMs) : void 0;
39
+ const relay = () => controller.abort();
40
+ if (params.signal) if (params.signal.aborted) controller.abort();
41
+ else params.signal.addEventListener("abort", relay, { once: true });
42
+ const response = await fetcher(parsed.toString(), {
43
+ ...params.init ?? {},
44
+ signal: controller.signal
45
+ });
46
+ const finalUrl = response.url || parsed.toString();
47
+ const finalHostname = new URL(finalUrl).hostname;
48
+ if (allowedHostnames.length > 0 && !allowedHostnames.includes(finalHostname)) {
49
+ clearTimeout(timeoutId);
50
+ if (params.signal) params.signal.removeEventListener("abort", relay);
51
+ throw new Error(`${params.auditContext ?? "guarded-fetch"} blocked redirected hostname "${finalHostname}"`);
52
+ }
53
+ return {
54
+ response,
55
+ finalUrl,
56
+ release: async () => {
57
+ clearTimeout(timeoutId);
58
+ if (params.signal) params.signal.removeEventListener("abort", relay);
59
+ }
60
+ };
61
+ }
62
+ //#endregion
63
+ export { fetchWithSsrFGuard, withTempDownloadPath };
@@ -0,0 +1,126 @@
1
+ //#region src/nextclaw-sdk/network-webhook.ts
2
+ function pruneMapToMaxSize(map, maxSize) {
3
+ while (map.size > maxSize) {
4
+ const firstKey = map.keys().next().value;
5
+ if (firstKey === void 0) break;
6
+ map.delete(firstKey);
7
+ }
8
+ }
9
+ const WEBHOOK_RATE_LIMIT_DEFAULTS = Object.freeze({
10
+ windowMs: 6e4,
11
+ maxRequests: 120,
12
+ maxTrackedKeys: 4096
13
+ });
14
+ const WEBHOOK_ANOMALY_COUNTER_DEFAULTS = Object.freeze({
15
+ maxTrackedKeys: 4096,
16
+ ttlMs: 360 * 6e4,
17
+ logEvery: 25
18
+ });
19
+ function createFixedWindowRateLimiter(options) {
20
+ const state = /* @__PURE__ */ new Map();
21
+ const windowMs = Math.max(1, Math.floor(options.windowMs));
22
+ const maxRequests = Math.max(1, Math.floor(options.maxRequests));
23
+ const maxTrackedKeys = Math.max(1, Math.floor(options.maxTrackedKeys));
24
+ const pruneIntervalMs = Math.max(1, Math.floor(options.pruneIntervalMs ?? windowMs));
25
+ let lastPruneMs = 0;
26
+ const touch = (key, value) => {
27
+ state.delete(key);
28
+ state.set(key, value);
29
+ };
30
+ const prune = (nowMs) => {
31
+ for (const [key, entry] of state) if (nowMs - entry.windowStartMs >= windowMs) state.delete(key);
32
+ };
33
+ return {
34
+ isRateLimited(key, nowMs = Date.now()) {
35
+ if (!key) return false;
36
+ if (nowMs - lastPruneMs >= pruneIntervalMs) {
37
+ prune(nowMs);
38
+ lastPruneMs = nowMs;
39
+ }
40
+ const existing = state.get(key);
41
+ if (!existing || nowMs - existing.windowStartMs >= windowMs) {
42
+ touch(key, {
43
+ count: 1,
44
+ windowStartMs: nowMs
45
+ });
46
+ pruneMapToMaxSize(state, maxTrackedKeys);
47
+ return false;
48
+ }
49
+ const nextCount = existing.count + 1;
50
+ touch(key, {
51
+ count: nextCount,
52
+ windowStartMs: existing.windowStartMs
53
+ });
54
+ pruneMapToMaxSize(state, maxTrackedKeys);
55
+ return nextCount > maxRequests;
56
+ },
57
+ size: () => state.size,
58
+ clear: () => {
59
+ state.clear();
60
+ lastPruneMs = 0;
61
+ }
62
+ };
63
+ }
64
+ function createWebhookAnomalyTracker(options) {
65
+ const trackedStatusCodes = new Set(options?.trackedStatusCodes ?? [
66
+ 400,
67
+ 401,
68
+ 408,
69
+ 413,
70
+ 415,
71
+ 429
72
+ ]);
73
+ const counters = /* @__PURE__ */ new Map();
74
+ const maxTrackedKeys = Math.max(1, Math.floor(options?.maxTrackedKeys ?? WEBHOOK_ANOMALY_COUNTER_DEFAULTS.maxTrackedKeys));
75
+ const ttlMs = Math.max(0, Math.floor(options?.ttlMs ?? WEBHOOK_ANOMALY_COUNTER_DEFAULTS.ttlMs));
76
+ const logEvery = Math.max(1, Math.floor(options?.logEvery ?? WEBHOOK_ANOMALY_COUNTER_DEFAULTS.logEvery));
77
+ const prune = (nowMs) => {
78
+ if (ttlMs <= 0) return;
79
+ for (const [key, entry] of counters) if (nowMs - entry.updatedAtMs >= ttlMs) counters.delete(key);
80
+ };
81
+ return {
82
+ record(params) {
83
+ if (!trackedStatusCodes.has(params.statusCode)) return 0;
84
+ const nowMs = params.nowMs ?? Date.now();
85
+ prune(nowMs);
86
+ const nextCount = (counters.get(params.key)?.count ?? 0) + 1;
87
+ counters.set(params.key, {
88
+ count: nextCount,
89
+ updatedAtMs: nowMs
90
+ });
91
+ pruneMapToMaxSize(counters, maxTrackedKeys);
92
+ if (params.log && (nextCount === 1 || nextCount % logEvery === 0)) params.log(params.message(nextCount));
93
+ return nextCount;
94
+ },
95
+ size: () => counters.size,
96
+ clear: () => counters.clear()
97
+ };
98
+ }
99
+ function isJsonContentType(value) {
100
+ const first = Array.isArray(value) ? value[0] : value;
101
+ if (!first) return false;
102
+ const mediaType = first.split(";", 1)[0]?.trim().toLowerCase();
103
+ return mediaType === "application/json" || Boolean(mediaType?.endsWith("+json"));
104
+ }
105
+ function applyBasicWebhookRequestGuards(params) {
106
+ const allowMethods = params.allowMethods?.length ? params.allowMethods : null;
107
+ if (allowMethods && !allowMethods.includes(params.req.method ?? "")) {
108
+ params.res.statusCode = 405;
109
+ params.res.setHeader("Allow", allowMethods.join(", "));
110
+ params.res.end("Method Not Allowed");
111
+ return false;
112
+ }
113
+ if (params.rateLimiter && params.rateLimitKey && params.rateLimiter.isRateLimited(params.rateLimitKey, params.nowMs ?? Date.now())) {
114
+ params.res.statusCode = 429;
115
+ params.res.end("Too Many Requests");
116
+ return false;
117
+ }
118
+ if (params.requireJsonContentType && params.req.method === "POST" && !isJsonContentType(params.req.headers["content-type"])) {
119
+ params.res.statusCode = 415;
120
+ params.res.end("Unsupported Media Type");
121
+ return false;
122
+ }
123
+ return true;
124
+ }
125
+ //#endregion
126
+ export { WEBHOOK_ANOMALY_COUNTER_DEFAULTS, WEBHOOK_RATE_LIMIT_DEFAULTS, applyBasicWebhookRequestGuards, createFixedWindowRateLimiter, createWebhookAnomalyTracker };
@@ -0,0 +1,4 @@
1
+ import "./network-fetch.js";
2
+ import "./network-webhook.js";
3
+ import "./network-body.js";
4
+ export {};
@@ -0,0 +1,21 @@
1
+ //#region src/nextclaw-sdk/runtime-store.ts
2
+ function createPluginRuntimeStore(errorMessage) {
3
+ let runtime = null;
4
+ return {
5
+ setRuntime(next) {
6
+ runtime = next;
7
+ },
8
+ clearRuntime() {
9
+ runtime = null;
10
+ },
11
+ tryGetRuntime() {
12
+ return runtime;
13
+ },
14
+ getRuntime() {
15
+ if (!runtime) throw new Error(errorMessage);
16
+ return runtime;
17
+ }
18
+ };
19
+ }
20
+ //#endregion
21
+ export { createPluginRuntimeStore };
@@ -0,0 +1,65 @@
1
+ //#region src/nextclaw-sdk/secrets-config.ts
2
+ function mergeAllowFromEntries(current, additions) {
3
+ const merged = [...current ?? [], ...additions].map((entry) => String(entry).trim()).filter(Boolean);
4
+ return [...new Set(merged)];
5
+ }
6
+ function splitOnboardingEntries(raw) {
7
+ return raw.split(/[\n,;]+/g).map((entry) => entry.trim()).filter(Boolean);
8
+ }
9
+ function patchTopLevelChannelConfig(params) {
10
+ const channelConfig = params.cfg.channels?.[params.channel] ?? {};
11
+ return {
12
+ ...params.cfg,
13
+ channels: {
14
+ ...params.cfg.channels,
15
+ [params.channel]: {
16
+ ...channelConfig,
17
+ ...params.enabled ? { enabled: true } : {},
18
+ ...params.patch
19
+ }
20
+ }
21
+ };
22
+ }
23
+ function addWildcardAllowFrom(allowFrom) {
24
+ const next = (allowFrom ?? []).map((entry) => String(entry).trim()).filter(Boolean);
25
+ if (!next.includes("*")) next.push("*");
26
+ return next;
27
+ }
28
+ function setTopLevelChannelAllowFrom(params) {
29
+ return patchTopLevelChannelConfig({
30
+ cfg: params.cfg,
31
+ channel: params.channel,
32
+ enabled: params.enabled,
33
+ patch: { allowFrom: params.allowFrom }
34
+ });
35
+ }
36
+ function setTopLevelChannelDmPolicyWithAllowFrom(params) {
37
+ const channelConfig = params.cfg.channels?.[params.channel] ?? {};
38
+ const existingAllowFrom = params.getAllowFrom?.(params.cfg) ?? channelConfig.allowFrom ?? void 0;
39
+ const allowFrom = params.dmPolicy === "open" ? addWildcardAllowFrom(existingAllowFrom) : void 0;
40
+ return patchTopLevelChannelConfig({
41
+ cfg: params.cfg,
42
+ channel: params.channel,
43
+ patch: {
44
+ dmPolicy: params.dmPolicy,
45
+ ...allowFrom ? { allowFrom } : {}
46
+ }
47
+ });
48
+ }
49
+ function setTopLevelChannelGroupPolicy(params) {
50
+ return patchTopLevelChannelConfig({
51
+ cfg: params.cfg,
52
+ channel: params.channel,
53
+ enabled: params.enabled,
54
+ patch: { groupPolicy: params.groupPolicy }
55
+ });
56
+ }
57
+ function buildSingleChannelSecretPromptState(params) {
58
+ return {
59
+ accountConfigured: params.accountConfigured,
60
+ hasConfigToken: params.hasConfigToken,
61
+ canUseEnv: params.allowEnv && Boolean(params.envValue?.trim()) && !params.hasConfigToken
62
+ };
63
+ }
64
+ //#endregion
65
+ export { buildSingleChannelSecretPromptState, mergeAllowFromEntries, setTopLevelChannelAllowFrom, setTopLevelChannelDmPolicyWithAllowFrom, setTopLevelChannelGroupPolicy, splitOnboardingEntries };
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,68 @@
1
+ import "zod";
2
+ //#region src/nextclaw-sdk/secrets-core.ts
3
+ const DEFAULT_SECRET_PROVIDER_ALIAS = "default";
4
+ const ENV_SECRET_REF_ID_RE = /^[A-Z][A-Z0-9_]{0,127}$/;
5
+ const ENV_SECRET_TEMPLATE_RE = /^\$\{([A-Z][A-Z0-9_]{0,127})\}$/;
6
+ const FILE_SECRET_REF_SEGMENT_PATTERN = /^(?:[^~]|~0|~1)*$/;
7
+ const EXEC_SECRET_REF_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$/;
8
+ function isRecord(value) {
9
+ return typeof value === "object" && value !== null && !Array.isArray(value);
10
+ }
11
+ function isSecretRef(value) {
12
+ return isRecord(value) && (value.source === "env" || value.source === "file" || value.source === "exec") && typeof value.provider === "string" && value.provider.trim().length > 0 && typeof value.id === "string" && value.id.trim().length > 0;
13
+ }
14
+ function coerceSecretRef(value, defaults) {
15
+ if (isSecretRef(value)) return value;
16
+ if (typeof value === "string") {
17
+ const match = ENV_SECRET_TEMPLATE_RE.exec(value.trim());
18
+ if (match) return {
19
+ source: "env",
20
+ provider: defaults?.env ?? "default",
21
+ id: match[1]
22
+ };
23
+ }
24
+ if (!isRecord(value)) return null;
25
+ if ((value.source === "env" || value.source === "file" || value.source === "exec") && typeof value.id === "string" && value.id.trim().length > 0 && value.provider === void 0) {
26
+ const source = value.source;
27
+ return {
28
+ source,
29
+ provider: source === "env" ? defaults?.env ?? "default" : source === "file" ? defaults?.file ?? "default" : defaults?.exec ?? "default",
30
+ id: value.id
31
+ };
32
+ }
33
+ return null;
34
+ }
35
+ function resolveSecretInputRef(params) {
36
+ return coerceSecretRef(params.refValue, params.defaults) ?? coerceSecretRef(params.value, params.defaults);
37
+ }
38
+ function normalizeSecretInputString(value) {
39
+ if (typeof value !== "string") return;
40
+ return value.trim() || void 0;
41
+ }
42
+ function hasConfiguredSecretInput(value, defaults) {
43
+ return Boolean(normalizeSecretInputString(value) || coerceSecretRef(value, defaults));
44
+ }
45
+ function normalizeResolvedSecretInputString(params) {
46
+ const normalized = normalizeSecretInputString(params.value);
47
+ if (normalized) return normalized;
48
+ const ref = resolveSecretInputRef(params);
49
+ if (ref) throw new Error(`${params.path}: unresolved SecretRef "${ref.source}:${ref.provider}:${ref.id}".`);
50
+ }
51
+ function isValidFileSecretRefId(value) {
52
+ if (value === "value") return true;
53
+ if (!value.startsWith("/")) return false;
54
+ return value.slice(1).split("/").every((segment) => FILE_SECRET_REF_SEGMENT_PATTERN.test(segment));
55
+ }
56
+ function isValidExecSecretRefId(value) {
57
+ if (!EXEC_SECRET_REF_ID_PATTERN.test(value)) return false;
58
+ return value.split("/").every((segment) => segment !== "." && segment !== "..");
59
+ }
60
+ function formatExecSecretRefIdValidationMessage() {
61
+ return [
62
+ "Exec secret reference id must match /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$/",
63
+ "and must not include \".\" or \"..\" path segments",
64
+ "(example: \"vault/openai/api-key\")."
65
+ ].join(" ");
66
+ }
67
+ //#endregion
68
+ export { DEFAULT_SECRET_PROVIDER_ALIAS, ENV_SECRET_REF_ID_RE, formatExecSecretRefIdValidationMessage, hasConfiguredSecretInput, isValidExecSecretRefId, isValidFileSecretRefId, normalizeResolvedSecretInputString, normalizeSecretInputString };