@noodleseed/one 0.185.0 → 0.186.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/node_modules/@noodle-borg/admission-limits/dist/in-memory-counter-store.d.ts +12 -4
- package/node_modules/@noodle-borg/admission-limits/dist/in-memory-counter-store.js +12 -4
- package/node_modules/@noodle-borg/admission-limits/dist/portable.d.ts +1 -0
- package/node_modules/@noodle-borg/admission-limits/dist/portable.js +1 -0
- package/node_modules/@noodle-borg/admission-limits/dist/public-mcp-admission.d.ts +18 -0
- package/node_modules/@noodle-borg/admission-limits/dist/public-mcp-admission.js +16 -0
- package/node_modules/@noodle-borg/module/dist/contract.d.ts +1 -1
- package/node_modules/@noodle-borg/service/dist/admission.js +47 -10
- package/node_modules/@noodle-borg/service/dist/channels/capability-report.js +1 -1
- package/node_modules/@noodle-borg/service/dist/channels/composition.js +2 -2
- package/node_modules/@noodle-borg/service/dist/channels/runtime.js +3 -3
- package/node_modules/@noodle-borg/service/dist/routes/business-information-channels.js +8 -9
- package/node_modules/@noodle-borg/service/dist/service.js +7 -3
- package/node_modules/@noodle-borg/transport-http/dist/responses.js +7 -5
- package/node_modules/@noodle-borg/wire-contracts/dist/application-channels.d.ts +33 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/application-channels.js +6 -3
- package/package.json +1 -1
|
@@ -2,13 +2,21 @@ import { type AtomicCounterAttemptOutcome, type AtomicDailyCounterStore, type Co
|
|
|
2
2
|
/**
|
|
3
3
|
* Process-local counters for local development and tests.
|
|
4
4
|
*
|
|
5
|
-
* `durable` is false, and that is load-bearing rather than informational:
|
|
6
|
-
*
|
|
7
|
-
* shared across instances would look like protection while
|
|
5
|
+
* `durable` is false by default, and that is load-bearing rather than informational: public surfaces
|
|
6
|
+
* (the website mint route, anonymous MCP tool calls) refuse to serve on this store, because a ceiling
|
|
7
|
+
* that resets on every restart and is not shared across instances would look like protection while
|
|
8
|
+
* providing none.
|
|
9
|
+
*
|
|
10
|
+
* `singleProcess: true` is the caller's claim that exactly one process serves and that losing today's
|
|
11
|
+
* counts on restart is acceptable, which makes the counters truthfully durable for that host. Only the
|
|
12
|
+
* local author loop (`noodle dev`) and tests may make that claim.
|
|
8
13
|
*/
|
|
9
14
|
export declare class InMemoryDailyCounterStore implements DailyCounterStore, AtomicDailyCounterStore {
|
|
10
15
|
#private;
|
|
11
|
-
readonly durable
|
|
16
|
+
readonly durable: boolean;
|
|
17
|
+
constructor(options?: {
|
|
18
|
+
readonly singleProcess?: true;
|
|
19
|
+
});
|
|
12
20
|
consume(request: CounterRequest, now: Date): Promise<CounterOutcome>;
|
|
13
21
|
consumeAll(requests: readonly CounterRequest[], now: Date): Promise<boolean>;
|
|
14
22
|
consumeAllOnce(requests: readonly CounterRequest[], attempt: {
|
|
@@ -2,14 +2,22 @@ import { counterRow, dayKey, nextReset, retentionCutoff, } from './counter-store
|
|
|
2
2
|
/**
|
|
3
3
|
* Process-local counters for local development and tests.
|
|
4
4
|
*
|
|
5
|
-
* `durable` is false, and that is load-bearing rather than informational:
|
|
6
|
-
*
|
|
7
|
-
* shared across instances would look like protection while
|
|
5
|
+
* `durable` is false by default, and that is load-bearing rather than informational: public surfaces
|
|
6
|
+
* (the website mint route, anonymous MCP tool calls) refuse to serve on this store, because a ceiling
|
|
7
|
+
* that resets on every restart and is not shared across instances would look like protection while
|
|
8
|
+
* providing none.
|
|
9
|
+
*
|
|
10
|
+
* `singleProcess: true` is the caller's claim that exactly one process serves and that losing today's
|
|
11
|
+
* counts on restart is acceptable, which makes the counters truthfully durable for that host. Only the
|
|
12
|
+
* local author loop (`noodle dev`) and tests may make that claim.
|
|
8
13
|
*/
|
|
9
14
|
export class InMemoryDailyCounterStore {
|
|
10
|
-
durable
|
|
15
|
+
durable;
|
|
11
16
|
#used = new Map();
|
|
12
17
|
#receipts = new Map();
|
|
18
|
+
constructor(options = {}) {
|
|
19
|
+
this.durable = options.singleProcess === true;
|
|
20
|
+
}
|
|
13
21
|
async consume(request, now) {
|
|
14
22
|
const row = counterRow(request, now);
|
|
15
23
|
const key = `${row.day}:${row.key}`;
|
|
@@ -4,6 +4,7 @@ export * from './counter-store.js';
|
|
|
4
4
|
export * from './envelope.js';
|
|
5
5
|
export * from './in-memory-counter-store.js';
|
|
6
6
|
export * from './public-admission-assertion.js';
|
|
7
|
+
export * from './public-mcp-admission.js';
|
|
7
8
|
export * from './public-record-admission.js';
|
|
8
9
|
export * from './request-attribution.js';
|
|
9
10
|
export * from './visitor-bucket.js';
|
|
@@ -4,6 +4,7 @@ export * from './counter-store.js';
|
|
|
4
4
|
export * from './envelope.js';
|
|
5
5
|
export * from './in-memory-counter-store.js';
|
|
6
6
|
export * from './public-admission-assertion.js';
|
|
7
|
+
export * from './public-mcp-admission.js';
|
|
7
8
|
export * from './public-record-admission.js';
|
|
8
9
|
export * from './request-attribution.js';
|
|
9
10
|
export * from './visitor-bucket.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anonymous callers of a public or mixed MCP app (ADR 0201 §7 applied to MCP).
|
|
3
|
+
*
|
|
4
|
+
* The daily ceiling is the solvency bound: it caps what anonymous traffic can spend of the business's
|
|
5
|
+
* monthly allowance without depending on client-address attribution, which host traffic (ChatGPT,
|
|
6
|
+
* Claude) makes meaningless. 10,000 a day is about a third of the Free plan's monthly pool per day, so
|
|
7
|
+
* signed-in callers always keep the rest.
|
|
8
|
+
*/
|
|
9
|
+
export declare const PUBLIC_MCP_ADMISSION_DEFAULTS: Readonly<{
|
|
10
|
+
anonymousToolCallsPerDay: 10000;
|
|
11
|
+
}>;
|
|
12
|
+
/** One budget per app environment, so a redeploy mid-day does not hand anonymous traffic a fresh one. */
|
|
13
|
+
export declare function anonymousMcpCounterKey(tenant: {
|
|
14
|
+
readonly org: string;
|
|
15
|
+
readonly app: string;
|
|
16
|
+
readonly env: string;
|
|
17
|
+
}): string;
|
|
18
|
+
//# sourceMappingURL=public-mcp-admission.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anonymous callers of a public or mixed MCP app (ADR 0201 §7 applied to MCP).
|
|
3
|
+
*
|
|
4
|
+
* The daily ceiling is the solvency bound: it caps what anonymous traffic can spend of the business's
|
|
5
|
+
* monthly allowance without depending on client-address attribution, which host traffic (ChatGPT,
|
|
6
|
+
* Claude) makes meaningless. 10,000 a day is about a third of the Free plan's monthly pool per day, so
|
|
7
|
+
* signed-in callers always keep the rest.
|
|
8
|
+
*/
|
|
9
|
+
export const PUBLIC_MCP_ADMISSION_DEFAULTS = Object.freeze({
|
|
10
|
+
anonymousToolCallsPerDay: 10_000,
|
|
11
|
+
});
|
|
12
|
+
/** One budget per app environment, so a redeploy mid-day does not hand anonymous traffic a fresh one. */
|
|
13
|
+
export function anonymousMcpCounterKey(tenant) {
|
|
14
|
+
return `mcp-anonymous:${tenant.org}/${tenant.app}/${tenant.env}`;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=public-mcp-admission.js.map
|
|
@@ -308,7 +308,7 @@ export type AdmissionDecision = {
|
|
|
308
308
|
} | {
|
|
309
309
|
readonly allow: false;
|
|
310
310
|
readonly reason: string;
|
|
311
|
-
readonly status?: 403 | 429;
|
|
311
|
+
readonly status?: 403 | 429 | 503;
|
|
312
312
|
readonly code?: -32001 | -32002 | -32003;
|
|
313
313
|
readonly policyId?: string;
|
|
314
314
|
readonly policyVersion?: number;
|
|
@@ -1,27 +1,64 @@
|
|
|
1
|
-
import { PUBLIC_RECORD_ADMISSION_DEFAULTS } from '@noodle-borg/admission-limits/portable';
|
|
1
|
+
import { anonymousMcpCounterKey, PUBLIC_MCP_ADMISSION_DEFAULTS, PUBLIC_RECORD_ADMISSION_DEFAULTS, } from '@noodle-borg/admission-limits/portable';
|
|
2
2
|
const ANONYMOUS_CONSUMER_LIMIT = PUBLIC_RECORD_ADMISSION_DEFAULTS.networkPerHour;
|
|
3
3
|
const ANONYMOUS_CONSUMER_WINDOW_MS = 60 * 60 * 1000;
|
|
4
|
-
export function createAdmissionGate(
|
|
4
|
+
export function createAdmissionGate(options) {
|
|
5
|
+
const { counters, audit, extraGate } = options;
|
|
6
|
+
const now = options.now ?? (() => new Date());
|
|
7
|
+
const anonymousLimiter = options.anonymousLimiter ?? new AnonymousConsumerLimiter(now);
|
|
8
|
+
const deny = async (context, decision) => {
|
|
9
|
+
await emitAdmissionDeny(audit, context, decision.reason, decision.status);
|
|
10
|
+
return decision;
|
|
11
|
+
};
|
|
5
12
|
return async (context) => {
|
|
6
13
|
if (extraGate !== undefined) {
|
|
7
14
|
const extra = await extraGate(context);
|
|
8
|
-
if (!extra.allow)
|
|
9
|
-
|
|
10
|
-
return extra;
|
|
11
|
-
}
|
|
15
|
+
if (!extra.allow)
|
|
16
|
+
return deny(context, extra);
|
|
12
17
|
}
|
|
13
18
|
if ((context.accessMode === 'public' || context.accessMode === 'mixed') &&
|
|
14
19
|
context.subject === undefined &&
|
|
15
20
|
context.method === 'tools/call') {
|
|
21
|
+
// A ceiling that resets on restart and is unshared across instances is no ceiling (ADR 0201 §7),
|
|
22
|
+
// so anonymous tool calls are refused outright rather than served without one.
|
|
23
|
+
if (!counters.durable)
|
|
24
|
+
return deny(context, { allow: false, reason: 'admission_store_not_durable', status: 503 });
|
|
16
25
|
const limited = anonymousLimiter.consume(`${context.routeId}:${context.remoteAddress ?? 'unknown'}`);
|
|
17
|
-
if (!limited.ok)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
if (!limited.ok)
|
|
27
|
+
return deny(context, { allow: false, reason: limited.code, status: 429 });
|
|
28
|
+
const daily = await consumeAnonymousDaily(counters, context, now());
|
|
29
|
+
if (daily !== undefined)
|
|
30
|
+
return deny(context, daily);
|
|
21
31
|
}
|
|
22
32
|
return { allow: true };
|
|
23
33
|
};
|
|
24
34
|
}
|
|
35
|
+
async function consumeAnonymousDaily(counters, context, now) {
|
|
36
|
+
const tenant = context.org !== undefined && context.app !== undefined && context.env !== undefined
|
|
37
|
+
? { org: context.org, app: context.app, env: context.env }
|
|
38
|
+
: undefined;
|
|
39
|
+
let outcome;
|
|
40
|
+
try {
|
|
41
|
+
outcome = await counters.consume({
|
|
42
|
+
key: tenant === undefined
|
|
43
|
+
? `mcp-anonymous:${context.routeId}`
|
|
44
|
+
: anonymousMcpCounterKey(tenant),
|
|
45
|
+
limit: PUBLIC_MCP_ADMISSION_DEFAULTS.anonymousToolCallsPerDay,
|
|
46
|
+
window: 'day',
|
|
47
|
+
}, now);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return { allow: false, reason: 'admission_unavailable', status: 503 };
|
|
51
|
+
}
|
|
52
|
+
if (outcome.allowed)
|
|
53
|
+
return undefined;
|
|
54
|
+
return {
|
|
55
|
+
allow: false,
|
|
56
|
+
reason: 'anonymous_daily_quota_exceeded',
|
|
57
|
+
status: 429,
|
|
58
|
+
retryAfterSeconds: Math.max(1, Math.ceil((outcome.resetAt.getTime() - now.getTime()) / 1000)),
|
|
59
|
+
resetAt: outcome.resetAt.toISOString(),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
25
62
|
async function emitAdmissionDeny(audit, context, reasonCode, status = 403) {
|
|
26
63
|
if (context.org === undefined)
|
|
27
64
|
return;
|
|
@@ -3,7 +3,7 @@ const ACCOUNT_SELF_SERVICE = {
|
|
|
3
3
|
status: 'unavailable',
|
|
4
4
|
code: 'IDENTITY_NOT_ESTABLISHABLE',
|
|
5
5
|
requirement: 'verified_customer',
|
|
6
|
-
next: 'Account questions are answered as limitations until
|
|
6
|
+
next: 'Account questions are answered as limitations until messaging identity linking ships.',
|
|
7
7
|
};
|
|
8
8
|
export async function capabilityReport(input) {
|
|
9
9
|
const { artifact } = input;
|
|
@@ -36,11 +36,11 @@ export function createWhatsAppRuntime(options, deps, history) {
|
|
|
36
36
|
: {}),
|
|
37
37
|
}, deps);
|
|
38
38
|
}
|
|
39
|
-
/** What business routes may do with
|
|
39
|
+
/** What business routes may do with messaging channels: read each one's projection and erase a participant's memory. */
|
|
40
40
|
export function whatsappBusinessRoutes(whatsapp) {
|
|
41
41
|
return whatsapp
|
|
42
42
|
? {
|
|
43
|
-
|
|
43
|
+
readMessaging: whatsapp.projection.bind(whatsapp),
|
|
44
44
|
forgetWhatsApp: whatsapp.forgetParticipant.bind(whatsapp),
|
|
45
45
|
}
|
|
46
46
|
: {};
|
|
@@ -172,13 +172,13 @@ export class WhatsAppRuntime {
|
|
|
172
172
|
throw new ChannelError('pause_before_webhook_change');
|
|
173
173
|
await (await this.provider(binding)).adapter.configureWebhook(this.webhookUrl(req, binding));
|
|
174
174
|
}
|
|
175
|
-
async projection(tenant) {
|
|
176
|
-
const binding = await this.channels.get(tenant,
|
|
175
|
+
async projection(tenant, channel) {
|
|
176
|
+
const binding = await this.channels.get(tenant, channel);
|
|
177
177
|
if (!binding)
|
|
178
178
|
return {
|
|
179
179
|
status: 'unavailable',
|
|
180
180
|
capabilities: [],
|
|
181
|
-
reason: 'WhatsApp is not configured
|
|
181
|
+
reason: `${channel === 'instagram' ? 'Instagram' : 'WhatsApp'} is not configured.`,
|
|
182
182
|
};
|
|
183
183
|
const view = {
|
|
184
184
|
bindingId: binding.id,
|
|
@@ -59,7 +59,12 @@ export async function projectBusinessChannels(installation, canEdit, base, deps)
|
|
|
59
59
|
};
|
|
60
60
|
const registered = await deps.registry.getActiveByTenant(tenant);
|
|
61
61
|
const target = registered && (await resolveTargetOrigins(registered));
|
|
62
|
-
const whatsapp = await
|
|
62
|
+
const [whatsapp, instagram] = await Promise.all([
|
|
63
|
+
deps.readMessaging?.(tenant, 'whatsapp'),
|
|
64
|
+
deps.readMessaging?.(tenant, 'instagram'),
|
|
65
|
+
]);
|
|
66
|
+
// Pausing agent channels pauses every messaging channel with them.
|
|
67
|
+
const intake = (view) => installation.intakeActive ? view : { ...view, status: 'paused' };
|
|
63
68
|
const shared = {
|
|
64
69
|
revision: installation.revision,
|
|
65
70
|
active: installation.intakeActive,
|
|
@@ -67,14 +72,8 @@ export async function projectBusinessChannels(installation, canEdit, base, deps)
|
|
|
67
72
|
...(deps.pageOrigin
|
|
68
73
|
? { businessPageUrl: `${deps.pageOrigin}/b/${installation.publicId}` }
|
|
69
74
|
: {}),
|
|
70
|
-
...(whatsapp
|
|
71
|
-
|
|
72
|
-
whatsapp: {
|
|
73
|
-
...whatsapp,
|
|
74
|
-
...(!installation.intakeActive ? { status: 'paused' } : {}),
|
|
75
|
-
},
|
|
76
|
-
}
|
|
77
|
-
: {}),
|
|
75
|
+
...(whatsapp ? { whatsapp: intake(whatsapp) } : {}),
|
|
76
|
+
...(instagram ? { instagram: intake(instagram) } : {}),
|
|
78
77
|
};
|
|
79
78
|
if (!target?.deploymentId)
|
|
80
79
|
return {
|
|
@@ -5,7 +5,7 @@ import { dispatchKnowledgeRequest } from '@noodle-borg/knowledge-operations/port
|
|
|
5
5
|
import { capabilityOperatorRoute } from '@noodle-borg/managed-capabilities';
|
|
6
6
|
import { OPENAI_APPS_CHALLENGE_PATH } from '@noodle-borg/module';
|
|
7
7
|
import { applySecurityHeaders, createMcpRouter, enforceHttps, noopLogger, sendJson, } from '@noodle-borg/transport-http';
|
|
8
|
-
import {
|
|
8
|
+
import { createAdmissionGate } from './admission.js';
|
|
9
9
|
import { createApplicationServingRuntime } from './application-runtime-target.js';
|
|
10
10
|
import { createArchivePreflight } from './archive-preflight.js';
|
|
11
11
|
import { ArchiveSweeper, resolveArchiveRetentionDays } from './archive-sweeper.js';
|
|
@@ -74,13 +74,17 @@ export function createServiceHandler(registry, options = {}) {
|
|
|
74
74
|
const [intentSettings, intentEvents, requestEvents, intentPreviewOrgs] = createObservabilityStores(options);
|
|
75
75
|
const assistantStore = options.assistantStore ?? new InMemoryAssistantStore();
|
|
76
76
|
const assistantAppearance = options.assistantAppearance ?? new InMemoryAssistantAppearanceSettingsStore();
|
|
77
|
-
const anonymousLimiter = new AnonymousConsumerLimiter();
|
|
78
77
|
const publicCounters = options.admissionCounters ?? new InMemoryDailyCounterStore();
|
|
79
78
|
const { businessInformationStore, businessInformationSourceStore, sourceCoordinator } = createBusinessInformationRuntime(registry, options, publicCounters);
|
|
80
79
|
const moduleProviders = createServiceModuleProviders({
|
|
81
80
|
options,
|
|
82
81
|
auditMirror: new StdoutAuditSink(logger),
|
|
83
|
-
admissionGate: createAdmissionGate(
|
|
82
|
+
admissionGate: createAdmissionGate({
|
|
83
|
+
counters: publicCounters,
|
|
84
|
+
audit: { emit: (event) => activeAudit.emit(event) },
|
|
85
|
+
...(options.admissionGate === undefined ? {} : { extraGate: options.admissionGate }),
|
|
86
|
+
...(options.clock === undefined ? {} : { now: options.clock }),
|
|
87
|
+
}),
|
|
84
88
|
});
|
|
85
89
|
const moduleHost = moduleProviders.host;
|
|
86
90
|
businessInformationStore?.configurePrincipalAuthority(moduleHost.platformHumanIdentity);
|
|
@@ -27,11 +27,13 @@ export function admissionRpcError(context, decision) {
|
|
|
27
27
|
(decision.status === 429
|
|
28
28
|
? LEGACY_MCP_ERROR.ADMISSION_QUOTA_EXCEEDED
|
|
29
29
|
: LEGACY_MCP_ERROR.ADMISSION_DENIED);
|
|
30
|
-
const message =
|
|
31
|
-
? '
|
|
32
|
-
: code === LEGACY_MCP_ERROR.
|
|
33
|
-
? '
|
|
34
|
-
:
|
|
30
|
+
const message = decision.status === 503
|
|
31
|
+
? 'Admission unavailable, try again shortly'
|
|
32
|
+
: code === LEGACY_MCP_ERROR.ADMISSION_QUOTA_EXCEEDED
|
|
33
|
+
? 'Quota exceeded'
|
|
34
|
+
: code === LEGACY_MCP_ERROR.ADMISSION_RATE_LIMITED
|
|
35
|
+
? 'Rate limited'
|
|
36
|
+
: 'Access denied by policy';
|
|
35
37
|
const data = { reason: decision.reason };
|
|
36
38
|
if (decision.policyId !== undefined)
|
|
37
39
|
data.policyId = decision.policyId;
|
|
@@ -49,6 +49,17 @@ export declare const ApplicationChannelsProjectionSchema: z.ZodObject<{
|
|
|
49
49
|
capabilities: z.ZodArray<z.ZodString>;
|
|
50
50
|
reason: z.ZodOptional<z.ZodString>;
|
|
51
51
|
}, z.core.$strict>>;
|
|
52
|
+
instagram: z.ZodOptional<z.ZodObject<{
|
|
53
|
+
status: z.ZodEnum<{
|
|
54
|
+
unavailable: "unavailable";
|
|
55
|
+
ready: "ready";
|
|
56
|
+
paused: "paused";
|
|
57
|
+
}>;
|
|
58
|
+
bindingId: z.ZodOptional<z.ZodString>;
|
|
59
|
+
revision: z.ZodOptional<z.ZodNumber>;
|
|
60
|
+
capabilities: z.ZodArray<z.ZodString>;
|
|
61
|
+
reason: z.ZodOptional<z.ZodString>;
|
|
62
|
+
}, z.core.$strict>>;
|
|
52
63
|
}, z.core.$strict>;
|
|
53
64
|
export type ApplicationChannelsProjection = z.infer<typeof ApplicationChannelsProjectionSchema>;
|
|
54
65
|
export declare const ApplicationChannelsResponseSchema: z.ZodObject<{
|
|
@@ -103,6 +114,17 @@ export declare const ApplicationChannelsResponseSchema: z.ZodObject<{
|
|
|
103
114
|
capabilities: z.ZodArray<z.ZodString>;
|
|
104
115
|
reason: z.ZodOptional<z.ZodString>;
|
|
105
116
|
}, z.core.$strict>>;
|
|
117
|
+
instagram: z.ZodOptional<z.ZodObject<{
|
|
118
|
+
status: z.ZodEnum<{
|
|
119
|
+
unavailable: "unavailable";
|
|
120
|
+
ready: "ready";
|
|
121
|
+
paused: "paused";
|
|
122
|
+
}>;
|
|
123
|
+
bindingId: z.ZodOptional<z.ZodString>;
|
|
124
|
+
revision: z.ZodOptional<z.ZodNumber>;
|
|
125
|
+
capabilities: z.ZodArray<z.ZodString>;
|
|
126
|
+
reason: z.ZodOptional<z.ZodString>;
|
|
127
|
+
}, z.core.$strict>>;
|
|
106
128
|
}, z.core.$strict>;
|
|
107
129
|
}, z.core.$strict>;
|
|
108
130
|
export declare const ApplicationChannelsClientResponseSchema: z.ZodObject<{
|
|
@@ -133,6 +155,17 @@ export declare const ApplicationChannelsClientResponseSchema: z.ZodObject<{
|
|
|
133
155
|
capabilities: z.ZodArray<z.ZodString>;
|
|
134
156
|
reason: z.ZodOptional<z.ZodString>;
|
|
135
157
|
}, z.core.$strip>>;
|
|
158
|
+
instagram: z.ZodOptional<z.ZodObject<{
|
|
159
|
+
status: z.ZodEnum<{
|
|
160
|
+
unavailable: "unavailable";
|
|
161
|
+
ready: "ready";
|
|
162
|
+
paused: "paused";
|
|
163
|
+
}>;
|
|
164
|
+
bindingId: z.ZodOptional<z.ZodString>;
|
|
165
|
+
revision: z.ZodOptional<z.ZodNumber>;
|
|
166
|
+
capabilities: z.ZodArray<z.ZodString>;
|
|
167
|
+
reason: z.ZodOptional<z.ZodString>;
|
|
168
|
+
}, z.core.$strip>>;
|
|
136
169
|
assistant: z.ZodObject<{
|
|
137
170
|
status: z.ZodEnum<{
|
|
138
171
|
unavailable: "unavailable";
|
|
@@ -39,7 +39,8 @@ const assistant = z
|
|
|
39
39
|
usage: usage.optional(),
|
|
40
40
|
})
|
|
41
41
|
.strict();
|
|
42
|
-
|
|
42
|
+
/** One messaging channel (WhatsApp, Instagram) of the application, without provider identifiers. */
|
|
43
|
+
const messaging = z.strictObject({
|
|
43
44
|
status,
|
|
44
45
|
bindingId: z.string().max(256).optional(),
|
|
45
46
|
revision: z.number().int().positive().optional(),
|
|
@@ -55,7 +56,8 @@ export const ApplicationChannelsProjectionSchema = z
|
|
|
55
56
|
deploymentId: z.string().min(1).max(256).optional(),
|
|
56
57
|
mcp,
|
|
57
58
|
assistant,
|
|
58
|
-
whatsapp:
|
|
59
|
+
whatsapp: messaging.optional(),
|
|
60
|
+
instagram: messaging.optional(),
|
|
59
61
|
})
|
|
60
62
|
.strict();
|
|
61
63
|
export const ApplicationChannelsResponseSchema = z
|
|
@@ -66,7 +68,8 @@ export const ApplicationChannelsClientResponseSchema = z
|
|
|
66
68
|
ok: z.literal(true),
|
|
67
69
|
data: ApplicationChannelsProjectionSchema.extend({
|
|
68
70
|
mcp: mcp.strip(),
|
|
69
|
-
whatsapp:
|
|
71
|
+
whatsapp: messaging.strip().optional(),
|
|
72
|
+
instagram: messaging.strip().optional(),
|
|
70
73
|
assistant: assistant
|
|
71
74
|
.extend({
|
|
72
75
|
usage: usage
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noodleseed/one",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.186.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Noodle CLI by Noodle Seed — author, run, and deploy declarative MCP servers. Embedding the assistant in your own web app is @noodleseed/assistant.",
|
|
6
6
|
"license": "Apache-2.0",
|