@noodleseed/one 0.183.1 → 0.185.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.
Files changed (63) hide show
  1. package/dist/commands/solutions-conversations.d.ts.map +1 -1
  2. package/dist/commands/solutions-conversations.js +3 -3
  3. package/dist/commands/solutions-conversations.js.map +1 -1
  4. package/dist/commands/solutions-history.d.ts.map +1 -1
  5. package/dist/commands/solutions-history.js +6 -3
  6. package/dist/commands/solutions-history.js.map +1 -1
  7. package/dist/commands/whatsapp-ops.d.ts +2 -1
  8. package/dist/commands/whatsapp-ops.d.ts.map +1 -1
  9. package/dist/commands/whatsapp-ops.js +63 -27
  10. package/dist/commands/whatsapp-ops.js.map +1 -1
  11. package/dist/commands/whatsapp-output.d.ts +1 -1
  12. package/dist/commands/whatsapp-output.d.ts.map +1 -1
  13. package/dist/commands/whatsapp-output.js +13 -7
  14. package/dist/commands/whatsapp-output.js.map +1 -1
  15. package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +1 -1
  16. package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +2 -2
  17. package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
  18. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-model-context.js +7 -2
  19. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-turn-context.d.ts +2 -1
  20. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-coordinator.d.ts +88 -8
  21. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-coordinator.js +42 -30
  22. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-spend.js +2 -2
  23. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-types.d.ts +60 -11
  24. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-types.js +31 -3
  25. package/node_modules/@noodle-borg/assistant-gateway/dist/model-request.d.ts +2 -1
  26. package/node_modules/@noodle-borg/assistant-gateway/dist/public-surface.d.ts +4 -2
  27. package/node_modules/@noodle-borg/assistant-gateway/dist/public-surface.js +4 -3
  28. package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +2 -1
  29. package/node_modules/@noodle-borg/authoring/dist/assistant.js +5 -4
  30. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-channels.js +72 -51
  31. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-conversations.js +1 -1
  32. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-history.js +1 -0
  33. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +16 -4
  34. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +8 -3
  35. package/node_modules/@noodle-borg/runtime/dist/connector/types.d.ts +1 -1
  36. package/node_modules/@noodle-borg/service/dist/application-history-settings.js +6 -2
  37. package/node_modules/@noodle-borg/service/dist/build-info.js +3 -2
  38. package/node_modules/@noodle-borg/service/dist/channels/instagram-meta.js +196 -0
  39. package/node_modules/@noodle-borg/service/dist/channels/instagram-webhook.js +78 -0
  40. package/node_modules/@noodle-borg/service/dist/channels/provider.js +1 -1
  41. package/node_modules/@noodle-borg/service/dist/channels/reply-bound.js +24 -0
  42. package/node_modules/@noodle-borg/service/dist/channels/runtime.js +35 -24
  43. package/node_modules/@noodle-borg/service/dist/conversation-history/capture.js +5 -5
  44. package/node_modules/@noodle-borg/service/dist/conversation-history/contracts.js +2 -1
  45. package/node_modules/@noodle-borg/service/dist/conversation-history/operator.js +2 -2
  46. package/node_modules/@noodle-borg/service/dist/routes/instagram-meta.js +79 -0
  47. package/node_modules/@noodle-borg/service/dist/routes/meta-webhook.js +36 -0
  48. package/node_modules/@noodle-borg/service/dist/routes/service-probes.js +4 -1
  49. package/node_modules/@noodle-borg/service/dist/routes/whatsapp-meta.js +3 -34
  50. package/node_modules/@noodle-borg/service/dist/routes/whatsapp.js +45 -24
  51. package/node_modules/@noodle-borg/service/dist/service.js +1 -1
  52. package/node_modules/@noodle-borg/service/package.json +1 -1
  53. package/node_modules/@noodle-borg/wire-contracts/dist/application-history-settings.d.ts +7 -0
  54. package/node_modules/@noodle-borg/wire-contracts/dist/application-history-settings.js +1 -0
  55. package/node_modules/@noodle-borg/wire-contracts/dist/conversation-history.d.ts +12 -0
  56. package/node_modules/@noodle-borg/wire-contracts/dist/conversation-history.js +1 -1
  57. package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +1 -0
  58. package/node_modules/@noodle-borg/wire-contracts/dist/index.js +1 -0
  59. package/node_modules/@noodle-borg/wire-contracts/dist/instagram.d.ts +181 -0
  60. package/node_modules/@noodle-borg/wire-contracts/dist/instagram.js +50 -0
  61. package/node_modules/@noodle-borg/wire-contracts/dist/service-info.d.ts +2 -0
  62. package/node_modules/@noodle-borg/wire-contracts/dist/service-info.js +3 -0
  63. package/package.json +3 -3
@@ -27,17 +27,14 @@ export type ChannelCapability = {
27
27
  readonly kind: 'tool' | 'knowledge';
28
28
  readonly name: string;
29
29
  };
30
- export interface ChannelBinding {
30
+ /** One binding per channel per environment; each channel owns its own provider asset. */
31
+ export declare const CHANNEL_KINDS: readonly ["whatsapp", "instagram"];
32
+ export type ChannelKind = (typeof CHANNEL_KINDS)[number];
33
+ export interface ChannelBindingBase {
31
34
  readonly id: string;
32
35
  readonly tenant: TenantRef;
33
- readonly provider: ChannelProvider;
34
- readonly phoneNumberId: string;
35
- /** The WhatsApp Business Account that owns the number; present only for `meta`. */
36
- readonly wabaId?: string | undefined;
37
36
  /** Tenant secret name of the provider credential: 360dialog key or Meta business token. */
38
37
  readonly apiKeySecret: string;
39
- /** Tenant secret name of the per-binding callback secret; present only for `360dialog`. */
40
- readonly webhookSecret?: string | undefined;
41
38
  readonly deploymentId: string;
42
39
  readonly capabilities: readonly ChannelCapability[];
43
40
  readonly supportEmail: string;
@@ -54,11 +51,35 @@ export interface ChannelBinding {
54
51
  readonly readyRevision?: number | undefined;
55
52
  readonly readyAt?: number | undefined;
56
53
  }
57
- export type ChannelConfigure = Pick<ChannelBinding, 'tenant' | 'phoneNumberId' | 'wabaId' | 'apiKeySecret' | 'webhookSecret' | 'deploymentId' | 'capabilities' | 'supportEmail'> & {
54
+ export interface WhatsAppBinding extends ChannelBindingBase {
55
+ /** Stored rows and operator responses omit it; `requireChannel` supplies it on read. */
56
+ readonly channel: 'whatsapp';
57
+ readonly provider: ChannelProvider;
58
+ readonly phoneNumberId: string;
59
+ /** The WhatsApp Business Account that owns the number; present only for `meta`. */
60
+ readonly wabaId?: string | undefined;
61
+ /** Tenant secret name of the per-binding callback secret; present only for `360dialog`. */
62
+ readonly webhookSecret?: string | undefined;
63
+ }
64
+ /** An Instagram professional account reached through its linked Facebook Page (Messenger Platform). */
65
+ export interface InstagramBinding extends ChannelBindingBase {
66
+ readonly channel: 'instagram';
67
+ readonly provider: 'meta';
68
+ readonly instagramAccountId: string;
69
+ readonly pageId: string;
70
+ }
71
+ export type ChannelBinding = WhatsAppBinding | InstagramBinding;
72
+ type ChannelConfigureBase = Pick<ChannelBindingBase, 'tenant' | 'apiKeySecret' | 'deploymentId' | 'capabilities' | 'supportEmail'> & {
73
+ readonly limits?: ChannelLimitsInput | undefined;
74
+ };
75
+ export type WhatsAppConfigure = ChannelConfigureBase & Pick<WhatsAppBinding, 'phoneNumberId' | 'wabaId' | 'webhookSecret'> & {
76
+ /** Absent in WhatsApp operator requests, so their idempotency fingerprints stay unchanged. */
77
+ readonly channel?: 'whatsapp' | undefined;
58
78
  /** Defaults to `360dialog`. */
59
79
  readonly provider?: ChannelProvider | undefined;
60
- readonly limits?: ChannelLimitsInput | undefined;
61
80
  };
81
+ export type InstagramConfigure = ChannelConfigureBase & Pick<InstagramBinding, 'channel' | 'instagramAccountId' | 'pageId'>;
82
+ export type ChannelConfigure = WhatsAppConfigure | InstagramConfigure;
62
83
  export interface ChannelAddress {
63
84
  readonly kind: 'phone' | 'opaque';
64
85
  readonly value: string;
@@ -120,7 +141,11 @@ export declare class ChannelError extends Error {
120
141
  constructor(code: string);
121
142
  }
122
143
  export declare function channelDigest(value: string): string;
123
- export declare function channelTenantKey(tenant: TenantRef): string;
144
+ /** WhatsApp keeps its original key so existing registry rows resolve without a migration. */
145
+ export declare function channelTenantKey(tenant: TenantRef, channel: ChannelKind): string;
146
+ /** The provider asset a binding owns; WhatsApp keeps its original unprefixed key. */
147
+ export declare function channelAssetKey(channel: ChannelKind, assetId: string): string;
148
+ export declare function channelBindingAsset(binding: ChannelBinding): string;
124
149
  export declare function channelParticipantId(binding: ChannelBinding, address: ChannelAddress): string;
125
150
  export declare function sameChannelTenant(left: TenantRef, right: TenantRef): boolean;
126
151
  export declare function channelLimits(input?: ChannelLimitsInput): ChannelLimits;
@@ -131,14 +156,37 @@ export declare function channelRow(id: string, kind: ChannelRow['kind'], value:
131
156
  expiresAt?: number;
132
157
  }): ChannelRow;
133
158
  export declare function writeChannelEvent(tx: ChannelTransaction, scope: string, event: ChannelEvent, now: number): Promise<void>;
159
+ export declare function channelBindingRow(binding: ChannelBinding, now: number): ChannelRow;
160
+ /** Rows written before Instagram carry no `channel`; they are WhatsApp. Nothing is written back. */
161
+ export declare function readChannelBinding(stored: unknown): ChannelBinding;
134
162
  export declare function publicChannelBinding(binding: ChannelBinding): {
163
+ channel: "instagram";
164
+ provider: "meta";
165
+ instagramAccountId: string;
166
+ pageId: string;
135
167
  id: string;
136
168
  tenant: TenantRef;
169
+ apiKeySecret: string;
170
+ deploymentId: string;
171
+ capabilities: readonly ChannelCapability[];
172
+ supportEmail: string;
173
+ revision: number;
174
+ generation: number;
175
+ state: "paused" | "enabled" | "disconnected";
176
+ limits: ChannelLimits;
177
+ createdAt: number;
178
+ updatedAt: number;
179
+ actor: string;
180
+ readyRevision?: number | undefined;
181
+ readyAt?: number | undefined;
182
+ } | {
137
183
  provider: ChannelProvider;
138
184
  phoneNumberId: string;
139
185
  wabaId?: string | undefined;
140
- apiKeySecret: string;
141
186
  webhookSecret?: string | undefined;
187
+ id: string;
188
+ tenant: TenantRef;
189
+ apiKeySecret: string;
142
190
  deploymentId: string;
143
191
  capabilities: readonly ChannelCapability[];
144
192
  supportEmail: string;
@@ -152,4 +200,5 @@ export declare function publicChannelBinding(binding: ChannelBinding): {
152
200
  readyRevision?: number | undefined;
153
201
  readyAt?: number | undefined;
154
202
  };
203
+ export {};
155
204
  //# sourceMappingURL=channel-types.d.ts.map
@@ -14,6 +14,8 @@ export const CHANNEL_DEFAULT_LIMITS = Object.freeze({
14
14
  textCharacters: 4000,
15
15
  dailyMicroUsd: 20_000_000,
16
16
  });
17
+ /** One binding per channel per environment; each channel owns its own provider asset. */
18
+ export const CHANNEL_KINDS = ['whatsapp', 'instagram'];
17
19
  export class ChannelError extends Error {
18
20
  code;
19
21
  constructor(code) {
@@ -25,8 +27,19 @@ export class ChannelError extends Error {
25
27
  export function channelDigest(value) {
26
28
  return createHash('sha256').update(value).digest('hex');
27
29
  }
28
- export function channelTenantKey(tenant) {
29
- return `tenant:${channelDigest(JSON.stringify([tenant.org, tenant.app, tenant.env]))}`;
30
+ /** WhatsApp keeps its original key so existing registry rows resolve without a migration. */
31
+ export function channelTenantKey(tenant, channel) {
32
+ const parts = [tenant.org, tenant.app, tenant.env];
33
+ return `tenant:${channelDigest(JSON.stringify(channel === 'whatsapp' ? parts : [...parts, channel]))}`;
34
+ }
35
+ /** The provider asset a binding owns; WhatsApp keeps its original unprefixed key. */
36
+ export function channelAssetKey(channel, assetId) {
37
+ return channel === 'whatsapp'
38
+ ? `asset:${channelDigest(assetId)}`
39
+ : `asset:${channel}:${channelDigest(assetId)}`;
40
+ }
41
+ export function channelBindingAsset(binding) {
42
+ return channelAssetKey(binding.channel, binding.channel === 'whatsapp' ? binding.phoneNumberId : binding.instagramAccountId);
30
43
  }
31
44
  export function channelParticipantId(binding, address) {
32
45
  return `p_${createHmac('sha256', Buffer.from(binding.indexKey, 'base64'))
@@ -65,8 +78,23 @@ export async function writeChannelEvent(tx, scope, event, now) {
65
78
  expiresAt: event.receivedAt + CHANNEL_RETENTION_MS,
66
79
  }));
67
80
  }
81
+ /** WhatsApp rows are stored without `channel`, byte-identical to rows written before Instagram. */
82
+ function storedChannelBinding(binding) {
83
+ if (binding.channel === 'instagram')
84
+ return binding;
85
+ const { channel: _whatsapp, ...stored } = binding;
86
+ return stored;
87
+ }
88
+ export function channelBindingRow(binding, now) {
89
+ return channelRow('binding', 'binding', storedChannelBinding(binding), now);
90
+ }
91
+ /** Rows written before Instagram carry no `channel`; they are WhatsApp. Nothing is written back. */
92
+ export function readChannelBinding(stored) {
93
+ const binding = stored;
94
+ return binding.channel === 'instagram' ? binding : { ...binding, channel: 'whatsapp' };
95
+ }
68
96
  export function publicChannelBinding(binding) {
69
- const { indexKey: _privateIndexKey, credentialDigest: _credentialDigest, ...visible } = binding;
97
+ const { indexKey: _privateIndexKey, credentialDigest: _credentialDigest, ...visible } = storedChannelBinding(binding);
70
98
  return visible;
71
99
  }
72
100
  //# sourceMappingURL=channel-types.js.map
@@ -1,3 +1,4 @@
1
+ import type { ChannelKind } from './channel-types.js';
1
2
  import { type ModelCompletion, type ModelToolCall, type ModelUsage } from './model-stream.js';
2
3
  export type AssistantModelSource = 'operator' | 'noodle-managed';
3
4
  export type AssistantModelTransport = 'chat-completions' | 'responses';
@@ -81,7 +82,7 @@ export type ResolvedAssistantModel = ResolvedAssistantModelBase & ({
81
82
  });
82
83
  export interface ManagedAssistantModelResolver {
83
84
  resolve(input: {
84
- readonly channel?: 'whatsapp';
85
+ readonly channel?: ChannelKind;
85
86
  readonly tenant: {
86
87
  readonly org: string;
87
88
  readonly app: string;
@@ -1,4 +1,5 @@
1
1
  import type { SurfaceCapabilityRef } from './artifact-projection.js';
2
+ import type { ChannelKind } from './channel-types.js';
2
3
  /**
3
4
  * The one reader of "does this deployment face the public, and on what terms".
4
5
  *
@@ -45,12 +46,13 @@ export declare function surfaceBindingForOrigin(assistant: unknown, origin: stri
45
46
  export declare function publicSurfaceOf(assistant: unknown): PublicSurface | undefined;
46
47
  export interface MessagingSurface {
47
48
  readonly kind: 'messaging';
48
- readonly channel: 'whatsapp';
49
+ readonly channel: ChannelKind;
49
50
  readonly mode: 'public';
50
51
  readonly capabilities: readonly SurfaceCapabilityRef[];
51
52
  readonly instructions?: string;
52
53
  /** @see PublicSurface.history */
53
54
  readonly history?: false;
54
55
  }
55
- export declare function messagingSurfaceOf(assistant: unknown): MessagingSurface | undefined;
56
+ /** The public messaging surface for one channel; each channel's binding reads only its own. */
57
+ export declare function messagingSurfaceOf(assistant: unknown, channel: ChannelKind): MessagingSurface | undefined;
56
58
  //# sourceMappingURL=public-surface.d.ts.map
@@ -67,18 +67,19 @@ export function publicSurfaceOf(assistant) {
67
67
  }
68
68
  return undefined;
69
69
  }
70
- export function messagingSurfaceOf(assistant) {
70
+ /** The public messaging surface for one channel; each channel's binding reads only its own. */
71
+ export function messagingSurfaceOf(assistant, channel) {
71
72
  const surfaces = assistant?.surfaces;
72
73
  if (!Array.isArray(surfaces))
73
74
  return undefined;
74
75
  for (const entry of surfaces) {
75
- if (entry?.kind !== 'messaging' || entry.channel !== 'whatsapp' || entry.mode !== 'public')
76
+ if (entry?.kind !== 'messaging' || entry.channel !== channel || entry.mode !== 'public')
76
77
  continue;
77
78
  if (!Array.isArray(entry.capabilities))
78
79
  return undefined;
79
80
  return {
80
81
  kind: 'messaging',
81
- channel: 'whatsapp',
82
+ channel,
82
83
  mode: 'public',
83
84
  capabilities: entry.capabilities,
84
85
  ...(typeof entry.instructions === 'string' ? { instructions: entry.instructions } : {}),
@@ -223,7 +223,8 @@ export interface PublicWebsiteAccess {
223
223
  readonly history?: false;
224
224
  }
225
225
  export interface PublicMessagingInput {
226
- readonly channel: 'whatsapp';
226
+ /** One surface per channel; the operator binds each channel's account separately. */
227
+ readonly channel: 'whatsapp' | 'instagram';
227
228
  readonly capabilities: readonly CapabilityRef[];
228
229
  readonly instructions?: string;
229
230
  /** @see AuthenticatedWebsiteInput.history */
@@ -9,7 +9,7 @@ export function publicMessaging(input) {
9
9
  throw new Error(`publicMessaging does not accept ${key}`);
10
10
  }
11
11
  }
12
- if (input.channel !== 'whatsapp')
12
+ if (input.channel !== 'whatsapp' && input.channel !== 'instagram')
13
13
  throw new Error('Unsupported public messaging channel');
14
14
  return {
15
15
  kind: 'messaging',
@@ -151,9 +151,10 @@ function assertDistinctSurfaces(surfaces) {
151
151
  throw new Error('embeddedAssistant requires at least one access surface');
152
152
  }
153
153
  const websites = surfaces.filter((surface) => surface.kind !== 'messaging');
154
- const messaging = surfaces.filter((surface) => surface.kind === 'messaging');
155
- if (messaging.length > 1)
156
- throw new Error('embeddedAssistant accepts only one whatsapp surface');
154
+ const channels = surfaces.flatMap((surface) => surface.kind === 'messaging' ? [surface.channel] : []);
155
+ const repeated = channels.find((channel, index) => channels.indexOf(channel) !== index);
156
+ if (repeated)
157
+ throw new Error(`embeddedAssistant accepts only one ${repeated} surface`);
157
158
  const publicSurfaces = websites.filter((surface) => surface.mode !== 'authenticated');
158
159
  if (publicSurfaces.length > 1) {
159
160
  throw new Error('embeddedAssistant accepts at most one public surface (public or mixed)');
@@ -41,6 +41,44 @@ const leaf = (name, summary, flags = []) => ({
41
41
  jsonOutput: { mode: 'single' },
42
42
  });
43
43
  const group = (name, summary, subcommands) => ({ name, summary, subcommands, flags: [], arguments: [] });
44
+ /** Every channel's verbs; only configuration and phone-based blocking differ. */
45
+ const operations = (configure, blocks) => [
46
+ leaf('status', 'Show configuration and current revision.'),
47
+ configure,
48
+ leaf('doctor', 'Check deployment, storage, worker, model cost bound, provider setup, and capability compatibility.', mutation),
49
+ leaf('enable', 'Enable after a successful current readiness check.', [revision, ...mutation]),
50
+ leaf('pause', 'Stop new work and unsent replies.', [revision, ...mutation]),
51
+ leaf('disconnect', 'Fence this binding and retain governed receipts.', [revision, ...mutation]),
52
+ group('webhook', 'Inspect or configure the owned provider callback.', [
53
+ leaf('inspect', 'Check URL and authentication without disclosing headers.'),
54
+ leaf('configure', 'Register the service-owned callback.', [revision, ...mutation]),
55
+ ]),
56
+ group('limits', 'Inspect or lower the approved limits.', [
57
+ leaf('get', 'Show limits.'),
58
+ leaf('set', 'Lower selected limits.', [revision, ...mutation, ...limits]),
59
+ ]),
60
+ leaf('usage', 'Show UTC-day settled and reserved inference spend.'),
61
+ group('blocks', 'Inspect local blocks.', [
62
+ leaf('list', 'List local and provider block evidence.', [
63
+ flag('after', 'Cursor from the previous page.'),
64
+ ]),
65
+ ]),
66
+ ...blocks,
67
+ group('cooldown', 'Inspect or clear an automatic cooldown.', [
68
+ leaf('inspect', 'Show a participant cooldown.', [participant]),
69
+ leaf('clear', 'Clear cooldown without resetting quotas.', [participant, ...mutation]),
70
+ ]),
71
+ group('events', 'Inspect content-free transport receipts.', [
72
+ leaf('list', 'List a page of events.', [
73
+ flag('after', 'Cursor returned by the previous page.'),
74
+ ]),
75
+ leaf('inspect', 'Inspect one event.', [flag('event-id', 'Event identifier.')]),
76
+ leaf('reconcile', 'Read current receipt evidence without resending.', [
77
+ flag('event-id', 'Event identifier.'),
78
+ ...mutation,
79
+ ]),
80
+ ]),
81
+ ];
44
82
  export const CHANNELS_COMMAND = {
45
83
  name: 'channels',
46
84
  section: 'operate',
@@ -48,44 +86,18 @@ export const CHANNELS_COMMAND = {
48
86
  arguments: [],
49
87
  flags: [],
50
88
  subcommands: [
51
- group('whatsapp', 'Configure a 360dialog or Meta Cloud API number, test readiness, and control public messaging.', [
52
- leaf('status', 'Show configuration and current revision.'),
53
- leaf('configure', 'Save a paused binding using managed secret references.', [
54
- revision,
55
- ...mutation,
56
- flag('provider', '360dialog (default) or meta for the Meta Cloud API.'),
57
- flag('phone-number-id', '360dialog/Meta phone-number asset ID.'),
58
- flag('waba-id', 'WhatsApp Business Account ID; required with --provider meta.'),
59
- flag('api-key-secret', 'Managed secret name for the 360dialog key or the Meta business token.'),
60
- flag('webhook-secret', 'Independent managed callback secret name; 360dialog only.'),
61
- flag('capabilities', 'Comma-separated knowledge:name or tool:name selections.'),
62
- flag('support-email', 'Contact shown when an answer cannot be completed.'),
63
- ...limits,
64
- ]),
65
- leaf('doctor', 'Check deployment, storage, worker, model cost bound, provider setup, and capability compatibility.', mutation),
66
- leaf('enable', 'Enable after a successful current readiness check.', [
67
- revision,
68
- ...mutation,
69
- ]),
70
- leaf('pause', 'Stop new work and unsent replies.', [revision, ...mutation]),
71
- leaf('disconnect', 'Fence this binding and retain governed receipts.', [
72
- revision,
73
- ...mutation,
74
- ]),
75
- group('webhook', 'Inspect or configure the owned provider callback.', [
76
- leaf('inspect', 'Check URL and authentication without disclosing headers.'),
77
- leaf('configure', 'Register the service-owned callback.', [revision, ...mutation]),
78
- ]),
79
- group('limits', 'Inspect or lower the approved limits.', [
80
- leaf('get', 'Show limits.'),
81
- leaf('set', 'Lower selected limits.', [revision, ...mutation, ...limits]),
82
- ]),
83
- leaf('usage', 'Show UTC-day settled and reserved inference spend.'),
84
- group('blocks', 'Inspect local blocks.', [
85
- leaf('list', 'List local and provider block evidence.', [
86
- flag('after', 'Cursor from the previous page.'),
87
- ]),
88
- ]),
89
+ group('whatsapp', 'Configure a 360dialog or Meta Cloud API number, test readiness, and control public messaging.', operations(leaf('configure', 'Save a paused binding using managed secret references.', [
90
+ revision,
91
+ ...mutation,
92
+ flag('provider', '360dialog (default) or meta for the Meta Cloud API.'),
93
+ flag('phone-number-id', '360dialog/Meta phone-number asset ID.'),
94
+ flag('waba-id', 'WhatsApp Business Account ID; required with --provider meta.'),
95
+ flag('api-key-secret', 'Managed secret name for the 360dialog key or the Meta business token.'),
96
+ flag('webhook-secret', 'Independent managed callback secret name; 360dialog only.'),
97
+ flag('capabilities', 'Comma-separated knowledge:name or tool:name selections.'),
98
+ flag('support-email', 'Contact shown when an answer cannot be completed.'),
99
+ ...limits,
100
+ ]), [
89
101
  leaf('block', 'Block locally; optionally request a provider block.', [
90
102
  participant,
91
103
  blockScope,
@@ -100,21 +112,30 @@ export const CHANNELS_COMMAND = {
100
112
  flag('phone-stdin', 'Supply the number if retained participant context has expired.', 'boolean'),
101
113
  ...mutation,
102
114
  ]),
103
- group('cooldown', 'Inspect or clear an automatic cooldown.', [
104
- leaf('inspect', 'Show a participant cooldown.', [participant]),
105
- leaf('clear', 'Clear cooldown without resetting quotas.', [participant, ...mutation]),
115
+ ])),
116
+ group('instagram', 'Configure an Instagram professional account through its Facebook Page, test readiness, and control DMs.', operations(leaf('configure', 'Save a paused binding using a managed secret reference.', [
117
+ revision,
118
+ ...mutation,
119
+ flag('instagram-account-id', 'Instagram professional account ID (17841...).'),
120
+ flag('page-id', 'ID of the Facebook Page linked to the account.'),
121
+ flag('api-key-secret', 'Managed secret name for the system-user token with Page access.'),
122
+ flag('capabilities', 'Comma-separated knowledge:name or tool:name selections.'),
123
+ flag('support-email', 'Contact shown when an answer cannot be completed.'),
124
+ ...limits,
125
+ ]), [
126
+ leaf('block', 'Block locally; Instagram has no provider block.', [
127
+ participant,
128
+ blockScope,
129
+ flag('until', 'Expiry as an ISO timestamp.'),
130
+ flag('indefinite', 'Explicitly retain this block until removed.', 'boolean'),
131
+ ...mutation,
106
132
  ]),
107
- group('events', 'Inspect content-free transport receipts.', [
108
- leaf('list', 'List a page of events.', [
109
- flag('after', 'Cursor returned by the previous page.'),
110
- ]),
111
- leaf('inspect', 'Inspect one event.', [flag('event-id', 'Event identifier.')]),
112
- leaf('reconcile', 'Read current receipt evidence without resending.', [
113
- flag('event-id', 'Event identifier.'),
114
- ...mutation,
115
- ]),
133
+ leaf('unblock', 'Remove an explicitly selected block without resetting usage.', [
134
+ participant,
135
+ blockScope,
136
+ ...mutation,
116
137
  ]),
117
- ]),
138
+ ])),
118
139
  ],
119
140
  };
120
141
  //# sourceMappingURL=catalog-data-channels.js.map
@@ -31,7 +31,7 @@ const paging = (maximum) => [
31
31
  type: 'string',
32
32
  value: '<channel>',
33
33
  summary: 'Only conversations from this channel.',
34
- constraints: { choices: ['website', 'whatsapp'] },
34
+ constraints: { choices: ['website', 'whatsapp', 'instagram'] },
35
35
  },
36
36
  {
37
37
  ...OPTIONAL_FLAG,
@@ -58,6 +58,7 @@ export const SOLUTION_HISTORY = {
58
58
  channel('website-visitors', 'Record anonymous website visitors.'),
59
59
  channel('signed-in-customers', 'Record signed-in customers.'),
60
60
  channel('whatsapp', 'Record WhatsApp conversations.'),
61
+ channel('instagram', 'Record Instagram DM conversations.'),
61
62
  {
62
63
  ...OPTIONAL_FLAG,
63
64
  required: true,
@@ -361,7 +361,10 @@ export declare const manifestV1Schema: z.ZodObject<{
361
361
  allowedOrigins: z.ZodArray<z.ZodString>;
362
362
  surfaces: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
363
363
  kind: z.ZodLiteral<"messaging">;
364
- channel: z.ZodLiteral<"whatsapp">;
364
+ channel: z.ZodEnum<{
365
+ whatsapp: "whatsapp";
366
+ instagram: "instagram";
367
+ }>;
365
368
  mode: z.ZodLiteral<"public">;
366
369
  capabilities: z.ZodArray<z.ZodObject<{
367
370
  kind: z.ZodEnum<{
@@ -868,7 +871,10 @@ export declare const manifestV2Schema: z.ZodObject<{
868
871
  allowedOrigins: z.ZodArray<z.ZodString>;
869
872
  surfaces: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
870
873
  kind: z.ZodLiteral<"messaging">;
871
- channel: z.ZodLiteral<"whatsapp">;
874
+ channel: z.ZodEnum<{
875
+ whatsapp: "whatsapp";
876
+ instagram: "instagram";
877
+ }>;
872
878
  mode: z.ZodLiteral<"public">;
873
879
  capabilities: z.ZodArray<z.ZodObject<{
874
880
  kind: z.ZodEnum<{
@@ -1669,7 +1675,10 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1669
1675
  allowedOrigins: z.ZodArray<z.ZodString>;
1670
1676
  surfaces: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
1671
1677
  kind: z.ZodLiteral<"messaging">;
1672
- channel: z.ZodLiteral<"whatsapp">;
1678
+ channel: z.ZodEnum<{
1679
+ whatsapp: "whatsapp";
1680
+ instagram: "instagram";
1681
+ }>;
1673
1682
  mode: z.ZodLiteral<"public">;
1674
1683
  capabilities: z.ZodArray<z.ZodObject<{
1675
1684
  kind: z.ZodEnum<{
@@ -2175,7 +2184,10 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
2175
2184
  allowedOrigins: z.ZodArray<z.ZodString>;
2176
2185
  surfaces: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
2177
2186
  kind: z.ZodLiteral<"messaging">;
2178
- channel: z.ZodLiteral<"whatsapp">;
2187
+ channel: z.ZodEnum<{
2188
+ whatsapp: "whatsapp";
2189
+ instagram: "instagram";
2190
+ }>;
2179
2191
  mode: z.ZodLiteral<"public">;
2180
2192
  capabilities: z.ZodArray<z.ZodObject<{
2181
2193
  kind: z.ZodEnum<{
@@ -196,7 +196,7 @@ const embeddedAssistantSchema = z
196
196
  z
197
197
  .object({
198
198
  kind: z.literal('messaging'),
199
- channel: z.literal('whatsapp'),
199
+ channel: z.enum(['whatsapp', 'instagram']),
200
200
  mode: z.literal('public'),
201
201
  capabilities: z.array(assistantCapabilitySchema),
202
202
  instructions: z.string().trim().min(1).max(MAX_SERVER_INSTRUCTIONS).optional(),
@@ -247,8 +247,13 @@ const embeddedAssistantSchema = z
247
247
  .strict()
248
248
  .superRefine((assistant, ctx) => {
249
249
  const messaging = assistant.surfaces?.filter((surface) => surface.kind === 'messaging') ?? [];
250
- if (messaging.length > 1)
251
- ctx.addIssue({ code: 'custom', path: ['surfaces'], message: 'duplicate whatsapp surface' });
250
+ const repeated = messaging.find((surface, index) => messaging.slice(0, index).some((earlier) => earlier.channel === surface.channel));
251
+ if (repeated)
252
+ ctx.addIssue({
253
+ code: 'custom',
254
+ path: ['surfaces'],
255
+ message: `duplicate ${repeated.channel} surface`,
256
+ });
252
257
  if (assistant.allowedOrigins.length === 0 && messaging.length === 0) {
253
258
  ctx.addIssue({
254
259
  code: 'custom',
@@ -92,7 +92,7 @@ export interface PublicAdmissionContext {
92
92
  readonly messaging?: string;
93
93
  readonly provenance?: {
94
94
  readonly kind: 'messaging';
95
- readonly channel: 'whatsapp';
95
+ readonly channel: 'whatsapp' | 'instagram';
96
96
  };
97
97
  }
98
98
  /** A single connector-operation invocation, fully resolved and credentialed by the runtime. */
@@ -1,9 +1,10 @@
1
- import { authenticatedSurfaceOf, messagingSurfaceOf, publicSurfaceOf, } from '@noodle-borg/assistant-gateway/portable';
1
+ import { authenticatedSurfaceOf, CHANNEL_KINDS, messagingSurfaceOf, publicSurfaceOf, } from '@noodle-borg/assistant-gateway/portable';
2
2
  import { ALL_CONVERSATION_SOURCES, } from './conversation-history/contracts.js';
3
3
  const WIRE_SOURCES = {
4
4
  websiteVisitors: 'website_visitors',
5
5
  signedInCustomers: 'signed_in_customers',
6
6
  whatsapp: 'whatsapp',
7
+ instagram: 'instagram',
7
8
  };
8
9
  /**
9
10
  * The surfaces the served application declared `history: false` on (ADR 0241 decision 11), so staff
@@ -15,7 +16,9 @@ export function historyDisabledSurfaces(assistant) {
15
16
  ...(authenticatedSurfaceOf(assistant)?.history === false
16
17
  ? ['authenticatedWebsite']
17
18
  : []),
18
- ...(messagingSurfaceOf(assistant)?.history === false ? ['publicMessaging'] : []),
19
+ ...(CHANNEL_KINDS.some((channel) => messagingSurfaceOf(assistant, channel)?.history === false)
20
+ ? ['publicMessaging']
21
+ : []),
19
22
  ];
20
23
  }
21
24
  /**
@@ -54,6 +57,7 @@ export function historySettingsProjection(setting, allowance, revision, canEdit)
54
57
  websiteVisitors: setting.sources.website_visitors,
55
58
  signedInCustomers: setting.sources.signed_in_customers,
56
59
  whatsapp: setting.sources.whatsapp,
60
+ instagram: setting.sources.instagram,
57
61
  },
58
62
  },
59
63
  };
@@ -1,12 +1,13 @@
1
1
  import { MIXED_CUSTOMER_AUTH_FEATURE_VERSION, serviceInfoResponseSchema, } from '@noodle-borg/wire-contracts';
2
- export function serviceInfoPayload(buildInfo, developerMcp, whatsapp = false) {
2
+ export function serviceInfoPayload(buildInfo, developerMcp, channels = {}) {
3
3
  return serviceInfoResponseSchema.parse({
4
4
  ok: true,
5
5
  status: 'ok',
6
6
  ...buildInfo,
7
7
  features: {
8
8
  mixedCustomerAuth: MIXED_CUSTOMER_AUTH_FEATURE_VERSION,
9
- ...(whatsapp ? { whatsapp: 1 } : {}),
9
+ ...(channels.whatsapp ? { whatsapp: 1 } : {}),
10
+ ...(channels.instagram ? { instagram: 1 } : {}),
10
11
  },
11
12
  ...(developerMcp
12
13
  ? { developerPlugin: { mcpCapabilityVersion: DEVELOPER_MCP_CAPABILITY_VERSION } }