@noodleseed/one 0.170.0 → 0.171.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 (105) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +3 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/catalog.d.ts.map +1 -1
  5. package/dist/commands/catalog.js +2 -1
  6. package/dist/commands/catalog.js.map +1 -1
  7. package/dist/commands/whatsapp-ops.d.ts +13 -0
  8. package/dist/commands/whatsapp-ops.d.ts.map +1 -0
  9. package/dist/commands/whatsapp-ops.js +244 -0
  10. package/dist/commands/whatsapp-ops.js.map +1 -0
  11. package/dist/commands/whatsapp-output.d.ts +3 -0
  12. package/dist/commands/whatsapp-output.d.ts.map +1 -0
  13. package/dist/commands/whatsapp-output.js +70 -0
  14. package/dist/commands/whatsapp-output.js.map +1 -0
  15. package/dist/dev.js +2 -0
  16. package/dist/dev.js.map +1 -1
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/plugin-mode/plugin-operation-contract.d.ts +1 -1
  22. package/node_modules/@noodle-borg/agent-kit/dist/curated/command-groups.js +4 -0
  23. package/node_modules/@noodle-borg/agent-kit/dist/curated/error-fixes.js +1 -0
  24. package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +1 -1
  25. package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js +3 -0
  26. package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +6 -0
  27. package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
  28. package/node_modules/@noodle-borg/app-audit/dist/audit-embedded-assistant.js +2 -0
  29. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-configuration.js +2 -1
  30. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-model-context.js +9 -4
  31. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-turn-context.d.ts +16 -0
  32. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-turn-context.js +19 -0
  33. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-coordinator.d.ts +145 -0
  34. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-coordinator.js +378 -0
  35. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-inbox.js +160 -0
  36. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-provider-blocks.d.ts +18 -0
  37. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-provider-blocks.js +69 -0
  38. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-spend.js +82 -0
  39. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-store.d.ts +42 -0
  40. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-store.js +76 -0
  41. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-types.d.ts +134 -0
  42. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-types.js +72 -0
  43. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-work.d.ts +20 -0
  44. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-work.js +221 -0
  45. package/node_modules/@noodle-borg/assistant-gateway/dist/messaging-tool-dispatch.d.ts +15 -0
  46. package/node_modules/@noodle-borg/assistant-gateway/dist/messaging-tool-dispatch.js +34 -0
  47. package/node_modules/@noodle-borg/assistant-gateway/dist/model-request.d.ts +19 -1
  48. package/node_modules/@noodle-borg/assistant-gateway/dist/model-request.js +22 -6
  49. package/node_modules/@noodle-borg/assistant-gateway/dist/portable.d.ts +7 -0
  50. package/node_modules/@noodle-borg/assistant-gateway/dist/portable.js +7 -0
  51. package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-assistant-appearance-store.js +83 -0
  52. package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-assistant-elevation.js +151 -0
  53. package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-assistant-interaction-writes.js +45 -0
  54. package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-assistant-interactions.js +340 -0
  55. package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-assistant.js +439 -0
  56. package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-channel-store.js +102 -0
  57. package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-continuity-store.js +159 -0
  58. package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-elevation-store.js +110 -0
  59. package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-embed-store.js +134 -0
  60. package/node_modules/@noodle-borg/assistant-gateway/dist/postgres.js +11 -0
  61. package/node_modules/@noodle-borg/assistant-gateway/dist/public-surface.d.ts +8 -0
  62. package/node_modules/@noodle-borg/assistant-gateway/dist/public-surface.js +23 -0
  63. package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +17 -2
  64. package/node_modules/@noodle-borg/authoring/dist/assistant.js +48 -19
  65. package/node_modules/@noodle-borg/authoring/dist/index.d.ts +1 -1
  66. package/node_modules/@noodle-borg/authoring/dist/index.js +1 -1
  67. package/node_modules/@noodle-borg/authoring/dist/load-authored.js +1 -1
  68. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-channels.js +121 -0
  69. package/node_modules/@noodle-borg/cli-catalog/dist/index.js +1 -0
  70. package/node_modules/@noodle-borg/compiler/dist/artifact/config.js +1 -1
  71. package/node_modules/@noodle-borg/compiler/dist/errors.d.ts +1 -1
  72. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +72 -12
  73. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +51 -27
  74. package/node_modules/@noodle-borg/compiler/dist/manifest/website-projection.d.ts +1 -0
  75. package/node_modules/@noodle-borg/compiler/dist/manifest/website-projection.js +19 -0
  76. package/node_modules/@noodle-borg/module-audit/dist/index.js +37 -0
  77. package/node_modules/@noodle-borg/module-audit/dist/postgres-audit.js +126 -0
  78. package/node_modules/@noodle-borg/module-audit/dist/route.js +66 -0
  79. package/node_modules/@noodle-borg/runtime/dist/managed-origins.js +8 -4
  80. package/node_modules/@noodle-borg/runtime/dist/postgres-state-handles.js +311 -0
  81. package/node_modules/@noodle-borg/runtime/dist/postgres.js +4 -0
  82. package/node_modules/@noodle-borg/service/dist/assistant-composition.js +18 -0
  83. package/node_modules/@noodle-borg/service/dist/build-info.js +5 -2
  84. package/node_modules/@noodle-borg/service/dist/business-information/source-ingestion-coordinator.js +7 -0
  85. package/node_modules/@noodle-borg/service/dist/channels/360dialog.js +233 -0
  86. package/node_modules/@noodle-borg/service/dist/channels/cipher.js +37 -0
  87. package/node_modules/@noodle-borg/service/dist/channels/composition.js +37 -0
  88. package/node_modules/@noodle-borg/service/dist/channels/inference-guard.js +53 -0
  89. package/node_modules/@noodle-borg/service/dist/channels/runtime.js +337 -0
  90. package/node_modules/@noodle-borg/service/dist/channels/worker-loop.js +50 -0
  91. package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +62 -34
  92. package/node_modules/@noodle-borg/service/dist/routes/assistant-model-binding.js +6 -2
  93. package/node_modules/@noodle-borg/service/dist/routes/business-information-channels.js +14 -1
  94. package/node_modules/@noodle-borg/service/dist/routes/whatsapp.js +238 -0
  95. package/node_modules/@noodle-borg/service/dist/service.js +30 -32
  96. package/node_modules/@noodle-borg/service/package.json +1 -1
  97. package/node_modules/@noodle-borg/wire-contracts/dist/application-channels.d.ts +33 -0
  98. package/node_modules/@noodle-borg/wire-contracts/dist/application-channels.js +9 -0
  99. package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +1 -0
  100. package/node_modules/@noodle-borg/wire-contracts/dist/index.js +1 -0
  101. package/node_modules/@noodle-borg/wire-contracts/dist/service-info.d.ts +2 -0
  102. package/node_modules/@noodle-borg/wire-contracts/dist/service-info.js +10 -2
  103. package/node_modules/@noodle-borg/wire-contracts/dist/whatsapp.d.ts +426 -0
  104. package/node_modules/@noodle-borg/wire-contracts/dist/whatsapp.js +213 -0
  105. package/package.json +2 -2
@@ -0,0 +1,121 @@
1
+ const flag = (name, summary, type = 'string') => ({
2
+ name,
3
+ summary,
4
+ type,
5
+ ...(type === 'boolean' ? {} : { value: '<value>' }),
6
+ required: false,
7
+ repeatable: false,
8
+ sensitive: name === 'auth-token',
9
+ aliases: [],
10
+ conflictsWith: [],
11
+ });
12
+ const scope = [
13
+ flag('org', 'Organization.'),
14
+ flag('app', 'Application.'),
15
+ flag('env', 'Environment.'),
16
+ flag('service', 'Service URL.'),
17
+ flag('auth-token', 'Control-plane token.'),
18
+ flag('json', 'Machine-readable output.', 'boolean'),
19
+ ];
20
+ const mutation = [flag('idempotency-key', 'Stable key for retrying this logical change.')];
21
+ const revision = flag('expected-revision', 'Current revision from status; zero when creating.', 'number');
22
+ const blockScope = flag('scope', 'local (default) or explicit provider block.');
23
+ const participant = flag('participant-id', 'Protected participant identifier from events list.');
24
+ const limits = [
25
+ 'per-minute',
26
+ 'per-hour',
27
+ 'per-day',
28
+ 'channel-per-day',
29
+ 'new-participants-per-day',
30
+ 'concurrent',
31
+ 'pending-per-participant',
32
+ 'pending',
33
+ 'text-characters',
34
+ 'daily-micro-usd',
35
+ ].map((name) => flag(name, `Lower the ${name} ceiling; zero disables this allowance.`, 'number'));
36
+ const leaf = (name, summary, flags = []) => ({
37
+ name,
38
+ summary,
39
+ flags: [...scope, ...flags],
40
+ arguments: [],
41
+ jsonOutput: { mode: 'single' },
42
+ });
43
+ const group = (name, summary, subcommands) => ({ name, summary, subcommands, flags: [], arguments: [] });
44
+ export const CHANNELS_COMMAND = {
45
+ name: 'channels',
46
+ section: 'operate',
47
+ summary: 'Connect and operate the same application on messaging channels.',
48
+ arguments: [],
49
+ flags: [],
50
+ subcommands: [
51
+ group('whatsapp', 'Configure the 360dialog channel, 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('phone-number-id', '360dialog/Meta phone-number asset ID.'),
57
+ flag('api-key-secret', 'Managed secret name for the 360dialog key.'),
58
+ flag('webhook-secret', 'Independent managed callback secret name.'),
59
+ flag('capabilities', 'Comma-separated knowledge:name or tool:name selections.'),
60
+ flag('support-email', 'Contact shown when an answer cannot be completed.'),
61
+ ...limits,
62
+ ]),
63
+ leaf('doctor', 'Check deployment, storage, worker, model cost bound, and provider setup.', mutation),
64
+ leaf('enable', 'Enable after a successful current readiness check.', [
65
+ revision,
66
+ ...mutation,
67
+ ]),
68
+ leaf('pause', 'Stop new work and unsent replies.', [revision, ...mutation]),
69
+ leaf('disconnect', 'Fence this binding and retain governed receipts.', [
70
+ revision,
71
+ ...mutation,
72
+ ]),
73
+ group('webhook', 'Inspect or configure the owned provider callback.', [
74
+ leaf('inspect', 'Check URL and authentication without disclosing headers.'),
75
+ leaf('configure', 'Register the service-owned callback.', [revision, ...mutation]),
76
+ ]),
77
+ group('limits', 'Inspect or lower the approved limits.', [
78
+ leaf('get', 'Show limits.'),
79
+ leaf('set', 'Lower selected limits.', [revision, ...mutation, ...limits]),
80
+ ]),
81
+ leaf('usage', 'Show UTC-day settled and reserved inference spend.'),
82
+ group('blocks', 'Inspect local blocks.', [
83
+ leaf('list', 'List local and provider block evidence.', [
84
+ flag('after', 'Cursor from the previous page.'),
85
+ ]),
86
+ ]),
87
+ leaf('block', 'Block locally; optionally request a provider block.', [
88
+ participant,
89
+ blockScope,
90
+ flag('phone-stdin', 'Read a phone number securely from standard input.', 'boolean'),
91
+ flag('until', 'Expiry as an ISO timestamp.'),
92
+ flag('indefinite', 'Explicitly retain this block until removed.', 'boolean'),
93
+ ...mutation,
94
+ ]),
95
+ leaf('unblock', 'Remove an explicitly selected block without resetting usage.', [
96
+ participant,
97
+ blockScope,
98
+ flag('phone-stdin', 'Supply the number if retained participant context has expired.', 'boolean'),
99
+ ...mutation,
100
+ ]),
101
+ group('cooldown', 'Inspect or clear an automatic cooldown.', [
102
+ leaf('inspect', 'Show a participant cooldown.', [participant]),
103
+ leaf('clear', 'Clear cooldown without resetting quotas.', [participant, ...mutation]),
104
+ ]),
105
+ group('events', 'Inspect content-free transport receipts.', [
106
+ leaf('list', 'List a page of events.', [
107
+ flag('after', 'Cursor returned by the previous page.'),
108
+ ]),
109
+ leaf('inspect', 'Inspect one event.', [flag('event-id', 'Event identifier.')]),
110
+ leaf('reconcile', 'Read current receipt evidence without resending.', [
111
+ flag('event-id', 'Event identifier.'),
112
+ ...mutation,
113
+ ]),
114
+ ]),
115
+ group('conversation', 'Erase conversation content.', [
116
+ leaf('forget', 'Erase participant context and message content while preserving safeguards.', [participant, ...mutation]),
117
+ ]),
118
+ ]),
119
+ ],
120
+ };
121
+ //# sourceMappingURL=catalog-data-channels.js.map
@@ -4,6 +4,7 @@ export * from './catalog-data-auth-discovery.js';
4
4
  export * from './catalog-data-billing.js';
5
5
  export * from './catalog-data-bootstrap-authoring.js';
6
6
  export * from './catalog-data-bootstrap-pre-auth.js';
7
+ export * from './catalog-data-channels.js';
7
8
  export * from './catalog-data-config.js';
8
9
  export * from './catalog-data-core.js';
9
10
  export * from './catalog-data-distributions.js';
@@ -20,7 +20,7 @@ function collectVariablesFromManagedOrigins(manifest, out) {
20
20
  const assistant = manifest.server.assistant;
21
21
  const values = [
22
22
  ...(assistant?.allowedOrigins ?? []),
23
- ...(assistant?.surfaces?.flatMap((surface) => surface.origins) ?? []),
23
+ ...(assistant?.surfaces?.flatMap((surface) => surface.kind === 'messaging' ? [] : surface.origins) ?? []),
24
24
  ...(manifest.handoff?.allowedDomains ?? []),
25
25
  ];
26
26
  for (const value of values) {
@@ -1,7 +1,7 @@
1
1
  import type { AppPackageArtifactV1 } from '@noodle-borg/app-package';
2
2
  import type { RuntimeArtifact } from './artifact/types.js';
3
3
  import type { PackagedAsset } from './assets.js';
4
- export type CompileErrorCode = 'invalid_context_provider' | 'yaml_parse_error' | 'invalid_shape' | 'invalid_name' | 'duplicate_name' | 'reserved_name' | 'unsupported_manifest_version' | 'reserved_for_future_version' | 'invalid_operation_ref' | 'external_ref' | 'invalid_schema_ref' | 'unknown_schema_ref' | 'schema_ref_conflict' | 'invalid_expression' | 'expr_unknown_root' | 'expr_root_unavailable' | 'expr_operator_not_allowed' | 'expr_if_not_boolean' | 'unknown_step_ref' | 'forward_step_ref' | 'self_step_ref' | 'duplicate_step_id' | 'invalid_fulfilment' | 'invalid_elicitation_schema' | 'invalid_elicitation_flow' | 'invalid_confirmation_flow' | 'arg_type_mismatch' | 'ambient_context_action' | 'duplicate_resource' | 'duplicate_prompt' | 'duplicate_resource_uri' | 'unsupported_uri_template' | 'duplicate_widget' | 'unknown_widget_tool' | 'unknown_widget_action_tool' | 'duplicate_widget_tool' | 'invalid_widget_binding' | 'invalid_widget_state_handle' | 'widget_html_too_large' | 'widget_html_total_too_large' | 'invalid_asset' | 'invalid_capability_requirement' | 'state_secret_field' | 'invalid_knowledge' | 'knowledge_unhashed' | 'invalid_managed_collection' | 'invalid_variable_declaration' | 'invalid_managed_collection_source' | 'unknown_connector_alias' | 'connector_not_in_catalog' | 'unknown_operation' | 'connector_binding_required' | 'ambiguous_nested_connector_binding' | 'invalid_connector_call_graph' | 'unsupported_credential_profile' | 'credential_scope_mismatch' | 'credential_audience_mismatch' | 'customer_endpoint_auth_required' | 'customer_endpoint_mapping_required' | 'customer_endpoint_unknown_mapping' | 'customer_endpoint_bridge_unsupported' | 'assistant_capability_unknown' | 'assistant_public_user_reference' | 'assistant_public_effect_unconfirmed' | 'customer_endpoint_action_unsupported' | 'customer_endpoint_surface_unsupported' | 'customer_endpoint_credential_source_unsupported' | 'customer_endpoint_policy_conflict' | 'customer_endpoint_routing_inconsistent' | 'unused_connector_alias' | 'arg_mismatch' | 'agent_guide_invalid' | 'agent_guide_duplicate_workflow' | 'agent_guide_duplicate_example' | 'agent_guide_example_workflow_missing' | 'agent_guide_capability_missing' | 'agent_guide_capability_kind' | 'app_package_sensitive_content';
4
+ export type CompileErrorCode = 'invalid_context_provider' | 'yaml_parse_error' | 'invalid_shape' | 'invalid_name' | 'duplicate_name' | 'reserved_name' | 'unsupported_manifest_version' | 'reserved_for_future_version' | 'invalid_operation_ref' | 'external_ref' | 'invalid_schema_ref' | 'unknown_schema_ref' | 'schema_ref_conflict' | 'invalid_expression' | 'expr_unknown_root' | 'expr_root_unavailable' | 'expr_operator_not_allowed' | 'expr_if_not_boolean' | 'unknown_step_ref' | 'forward_step_ref' | 'self_step_ref' | 'duplicate_step_id' | 'invalid_fulfilment' | 'invalid_elicitation_schema' | 'invalid_elicitation_flow' | 'invalid_confirmation_flow' | 'arg_type_mismatch' | 'ambient_context_action' | 'duplicate_resource' | 'duplicate_prompt' | 'duplicate_resource_uri' | 'unsupported_uri_template' | 'duplicate_widget' | 'unknown_widget_tool' | 'unknown_widget_action_tool' | 'duplicate_widget_tool' | 'invalid_widget_binding' | 'invalid_widget_state_handle' | 'widget_html_too_large' | 'widget_html_total_too_large' | 'invalid_asset' | 'invalid_capability_requirement' | 'state_secret_field' | 'invalid_knowledge' | 'knowledge_unhashed' | 'invalid_managed_collection' | 'invalid_variable_declaration' | 'invalid_managed_collection_source' | 'unknown_connector_alias' | 'connector_not_in_catalog' | 'unknown_operation' | 'connector_binding_required' | 'ambiguous_nested_connector_binding' | 'invalid_connector_call_graph' | 'unsupported_credential_profile' | 'credential_scope_mismatch' | 'credential_audience_mismatch' | 'customer_endpoint_auth_required' | 'customer_endpoint_mapping_required' | 'customer_endpoint_unknown_mapping' | 'customer_endpoint_bridge_unsupported' | 'assistant_messaging_unsupported' | 'assistant_capability_unknown' | 'assistant_public_user_reference' | 'assistant_public_effect_unconfirmed' | 'customer_endpoint_action_unsupported' | 'customer_endpoint_surface_unsupported' | 'customer_endpoint_credential_source_unsupported' | 'customer_endpoint_policy_conflict' | 'customer_endpoint_routing_inconsistent' | 'unused_connector_alias' | 'arg_mismatch' | 'agent_guide_invalid' | 'agent_guide_duplicate_workflow' | 'agent_guide_duplicate_example' | 'agent_guide_example_workflow_missing' | 'agent_guide_capability_missing' | 'agent_guide_capability_kind' | 'app_package_sensitive_content';
5
5
  export interface CompileError {
6
6
  readonly code: CompileErrorCode;
7
7
  /** Dotted path to the offending location (e.g. `tools.1.name`); empty string for the document root. */
@@ -358,10 +358,25 @@ export declare const manifestV1Schema: z.ZodObject<{
358
358
  kind: z.ZodLiteral<"noodle-managed">;
359
359
  }, z.core.$strict>]>;
360
360
  allowedOrigins: z.ZodArray<z.ZodString>;
361
- surfaces: z.ZodOptional<z.ZodArray<z.ZodObject<{
361
+ surfaces: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
362
+ kind: z.ZodLiteral<"messaging">;
363
+ channel: z.ZodLiteral<"whatsapp">;
364
+ mode: z.ZodLiteral<"public">;
365
+ capabilities: z.ZodArray<z.ZodObject<{
366
+ kind: z.ZodEnum<{
367
+ tool: "tool";
368
+ resource: "resource";
369
+ prompt: "prompt";
370
+ knowledge: "knowledge";
371
+ }>;
372
+ name: z.ZodString;
373
+ }, z.core.$strict>>;
374
+ instructions: z.ZodOptional<z.ZodString>;
375
+ }, z.core.$strict>, z.ZodObject<{
376
+ kind: z.ZodOptional<z.ZodLiteral<"website">>;
362
377
  mode: z.ZodEnum<{
363
- authenticated: "authenticated";
364
378
  public: "public";
379
+ authenticated: "authenticated";
365
380
  mixed: "mixed";
366
381
  }>;
367
382
  origins: z.ZodArray<z.ZodString>;
@@ -386,7 +401,7 @@ export declare const manifestV1Schema: z.ZodObject<{
386
401
  windowSeconds: z.ZodOptional<z.ZodNumber>;
387
402
  maxRestores: z.ZodOptional<z.ZodNumber>;
388
403
  }, z.core.$strict>>;
389
- }, z.core.$strict>>>;
404
+ }, z.core.$strict>]>>>;
390
405
  sessionClaims: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
391
406
  exposeToModel: z.ZodOptional<z.ZodBoolean>;
392
407
  }, z.core.$strict>>>;
@@ -847,10 +862,25 @@ export declare const manifestV2Schema: z.ZodObject<{
847
862
  kind: z.ZodLiteral<"noodle-managed">;
848
863
  }, z.core.$strict>]>;
849
864
  allowedOrigins: z.ZodArray<z.ZodString>;
850
- surfaces: z.ZodOptional<z.ZodArray<z.ZodObject<{
865
+ surfaces: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
866
+ kind: z.ZodLiteral<"messaging">;
867
+ channel: z.ZodLiteral<"whatsapp">;
868
+ mode: z.ZodLiteral<"public">;
869
+ capabilities: z.ZodArray<z.ZodObject<{
870
+ kind: z.ZodEnum<{
871
+ tool: "tool";
872
+ resource: "resource";
873
+ prompt: "prompt";
874
+ knowledge: "knowledge";
875
+ }>;
876
+ name: z.ZodString;
877
+ }, z.core.$strict>>;
878
+ instructions: z.ZodOptional<z.ZodString>;
879
+ }, z.core.$strict>, z.ZodObject<{
880
+ kind: z.ZodOptional<z.ZodLiteral<"website">>;
851
881
  mode: z.ZodEnum<{
852
- authenticated: "authenticated";
853
882
  public: "public";
883
+ authenticated: "authenticated";
854
884
  mixed: "mixed";
855
885
  }>;
856
886
  origins: z.ZodArray<z.ZodString>;
@@ -875,7 +905,7 @@ export declare const manifestV2Schema: z.ZodObject<{
875
905
  windowSeconds: z.ZodOptional<z.ZodNumber>;
876
906
  maxRestores: z.ZodOptional<z.ZodNumber>;
877
907
  }, z.core.$strict>>;
878
- }, z.core.$strict>>>;
908
+ }, z.core.$strict>]>>>;
879
909
  sessionClaims: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
880
910
  exposeToModel: z.ZodOptional<z.ZodBoolean>;
881
911
  }, z.core.$strict>>>;
@@ -1602,10 +1632,25 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1602
1632
  kind: z.ZodLiteral<"noodle-managed">;
1603
1633
  }, z.core.$strict>]>;
1604
1634
  allowedOrigins: z.ZodArray<z.ZodString>;
1605
- surfaces: z.ZodOptional<z.ZodArray<z.ZodObject<{
1635
+ surfaces: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
1636
+ kind: z.ZodLiteral<"messaging">;
1637
+ channel: z.ZodLiteral<"whatsapp">;
1638
+ mode: z.ZodLiteral<"public">;
1639
+ capabilities: z.ZodArray<z.ZodObject<{
1640
+ kind: z.ZodEnum<{
1641
+ tool: "tool";
1642
+ resource: "resource";
1643
+ prompt: "prompt";
1644
+ knowledge: "knowledge";
1645
+ }>;
1646
+ name: z.ZodString;
1647
+ }, z.core.$strict>>;
1648
+ instructions: z.ZodOptional<z.ZodString>;
1649
+ }, z.core.$strict>, z.ZodObject<{
1650
+ kind: z.ZodOptional<z.ZodLiteral<"website">>;
1606
1651
  mode: z.ZodEnum<{
1607
- authenticated: "authenticated";
1608
1652
  public: "public";
1653
+ authenticated: "authenticated";
1609
1654
  mixed: "mixed";
1610
1655
  }>;
1611
1656
  origins: z.ZodArray<z.ZodString>;
@@ -1630,7 +1675,7 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1630
1675
  windowSeconds: z.ZodOptional<z.ZodNumber>;
1631
1676
  maxRestores: z.ZodOptional<z.ZodNumber>;
1632
1677
  }, z.core.$strict>>;
1633
- }, z.core.$strict>>>;
1678
+ }, z.core.$strict>]>>>;
1634
1679
  sessionClaims: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
1635
1680
  exposeToModel: z.ZodOptional<z.ZodBoolean>;
1636
1681
  }, z.core.$strict>>>;
@@ -2090,10 +2135,25 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
2090
2135
  kind: z.ZodLiteral<"noodle-managed">;
2091
2136
  }, z.core.$strict>]>;
2092
2137
  allowedOrigins: z.ZodArray<z.ZodString>;
2093
- surfaces: z.ZodOptional<z.ZodArray<z.ZodObject<{
2138
+ surfaces: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
2139
+ kind: z.ZodLiteral<"messaging">;
2140
+ channel: z.ZodLiteral<"whatsapp">;
2141
+ mode: z.ZodLiteral<"public">;
2142
+ capabilities: z.ZodArray<z.ZodObject<{
2143
+ kind: z.ZodEnum<{
2144
+ tool: "tool";
2145
+ resource: "resource";
2146
+ prompt: "prompt";
2147
+ knowledge: "knowledge";
2148
+ }>;
2149
+ name: z.ZodString;
2150
+ }, z.core.$strict>>;
2151
+ instructions: z.ZodOptional<z.ZodString>;
2152
+ }, z.core.$strict>, z.ZodObject<{
2153
+ kind: z.ZodOptional<z.ZodLiteral<"website">>;
2094
2154
  mode: z.ZodEnum<{
2095
- authenticated: "authenticated";
2096
2155
  public: "public";
2156
+ authenticated: "authenticated";
2097
2157
  mixed: "mixed";
2098
2158
  }>;
2099
2159
  origins: z.ZodArray<z.ZodString>;
@@ -2118,7 +2178,7 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
2118
2178
  windowSeconds: z.ZodOptional<z.ZodNumber>;
2119
2179
  maxRestores: z.ZodOptional<z.ZodNumber>;
2120
2180
  }, z.core.$strict>>;
2121
- }, z.core.$strict>>>;
2181
+ }, z.core.$strict>]>>>;
2122
2182
  sessionClaims: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
2123
2183
  exposeToModel: z.ZodOptional<z.ZodBoolean>;
2124
2184
  }, z.core.$strict>>>;
@@ -277,38 +277,50 @@ const embeddedAssistantSchema = z
277
277
  .strict(),
278
278
  z.object({ kind: z.literal('noodle-managed') }).strict(),
279
279
  ]),
280
- allowedOrigins: z.array(assistantOriginOrVariableSchema).min(1),
280
+ allowedOrigins: z.array(assistantOriginOrVariableSchema),
281
281
  // The front doors this assistant serves (ADR 0201, amended 2026-08-12). Absent means the pre-0201
282
282
  // authenticated shape, so released artifacts keep their meaning; `allowedOrigins` stays the union
283
283
  // and `sessionClaims` stays mirrored, so the service reads both unchanged. `capabilities` is the
284
284
  // exact reachable surface: optional when authenticated, required when public or mixed.
285
285
  surfaces: z
286
- .array(z
287
- .object({
288
- mode: z.enum(['authenticated', 'public', 'mixed']),
289
- origins: z.array(assistantOriginOrVariableSchema).min(1),
290
- instructions: z.string().trim().min(1).max(MAX_SERVER_INSTRUCTIONS).optional(),
291
- capabilities: z.array(assistantCapabilitySchema).optional(),
292
- sessionClaims: sessionClaimsSchema.optional(),
293
- // Overrides the assistant's own opt-in for sessions minted on this surface, in either
294
- // direction (ADR 0220, amended). Declared explicitly rather than inherited from
295
- // `assistantUiSchema.shape` — this is not renderer presentation, and a surface that
296
- // acquired it by a spread would be an accident rather than a decision.
297
- webmcp: optionalStrictObject({ enabled: z.boolean().optional() }),
298
- // Anonymous cross-page display continuity (ADR 0223, clauses 11-16), declared per public
299
- // or mixed surface. These bounds mirror the structural ceilings the gateway clamps to at
300
- // runtime; the ADR is the single source, and this package cannot import the gateway
301
- // because the dependency runs the other way. Refusing rather than clamping is deliberate:
302
- // the gateway narrows an operator's value silently by design, but a developer who asks
303
- // for more than the ceiling has made a mistake, and silence would hide it until someone
304
- // measured the live behaviour.
305
- continuity: optionalStrictObject({
306
- enabled: z.boolean().optional(),
307
- windowSeconds: z.number().int().min(0).max(600).optional(),
308
- maxRestores: z.number().int().min(0).max(10).optional(),
309
- }),
310
- })
311
- .strict())
286
+ .array(z.union([
287
+ z
288
+ .object({
289
+ kind: z.literal('messaging'),
290
+ channel: z.literal('whatsapp'),
291
+ mode: z.literal('public'),
292
+ capabilities: z.array(assistantCapabilitySchema),
293
+ instructions: z.string().trim().min(1).max(MAX_SERVER_INSTRUCTIONS).optional(),
294
+ })
295
+ .strict(),
296
+ z
297
+ .object({
298
+ kind: z.literal('website').optional(),
299
+ mode: z.enum(['authenticated', 'public', 'mixed']),
300
+ origins: z.array(assistantOriginOrVariableSchema).min(1),
301
+ instructions: z.string().trim().min(1).max(MAX_SERVER_INSTRUCTIONS).optional(),
302
+ capabilities: z.array(assistantCapabilitySchema).optional(),
303
+ sessionClaims: sessionClaimsSchema.optional(),
304
+ // Overrides the assistant's own opt-in for sessions minted on this surface, in either
305
+ // direction (ADR 0220, amended). Declared explicitly rather than inherited from
306
+ // `assistantUiSchema.shape` — this is not renderer presentation, and a surface that
307
+ // acquired it by a spread would be an accident rather than a decision.
308
+ webmcp: optionalStrictObject({ enabled: z.boolean().optional() }),
309
+ // Anonymous cross-page display continuity (ADR 0223, clauses 11-16), declared per public
310
+ // or mixed surface. These bounds mirror the structural ceilings the gateway clamps to at
311
+ // runtime; the ADR is the single source, and this package cannot import the gateway
312
+ // because the dependency runs the other way. Refusing rather than clamping is deliberate:
313
+ // the gateway narrows an operator's value silently by design, but a developer who asks
314
+ // for more than the ceiling has made a mistake, and silence would hide it until someone
315
+ // measured the live behaviour.
316
+ continuity: optionalStrictObject({
317
+ enabled: z.boolean().optional(),
318
+ windowSeconds: z.number().int().min(0).max(600).optional(),
319
+ maxRestores: z.number().int().min(0).max(10).optional(),
320
+ }),
321
+ })
322
+ .strict(),
323
+ ]))
312
324
  .min(1)
313
325
  .optional(),
314
326
  // Mirrored from the authenticated surface; the session exchange reads it here (ADR 0141,
@@ -323,7 +335,19 @@ const embeddedAssistantSchema = z
323
335
  })
324
336
  .strict()
325
337
  .superRefine((assistant, ctx) => {
338
+ const messaging = assistant.surfaces?.filter((surface) => surface.kind === 'messaging') ?? [];
339
+ if (messaging.length > 1)
340
+ ctx.addIssue({ code: 'custom', path: ['surfaces'], message: 'duplicate whatsapp surface' });
341
+ if (assistant.allowedOrigins.length === 0 && messaging.length === 0) {
342
+ ctx.addIssue({
343
+ code: 'custom',
344
+ path: ['allowedOrigins'],
345
+ message: 'a website assistant requires allowedOrigins',
346
+ });
347
+ }
326
348
  assistant.surfaces?.forEach((surface, index) => {
349
+ if (surface.kind === 'messaging')
350
+ return;
327
351
  // The authenticated direction already reattaches through ADR 0223 clause 7, authorized by a
328
352
  // backend-verified subject rather than by possession of a handle. A `continuity` block here
329
353
  // would be a developer believing they configured something nothing reads, so refuse it rather
@@ -18,6 +18,7 @@ import type { CompileError } from '../errors.js';
18
18
  * ADR 0055's third class — a declared anonymous fallback — stays reserved, not produced.
19
19
  */
20
20
  interface AssistantSurfaceInput {
21
+ readonly kind?: 'website' | 'messaging' | undefined;
21
22
  readonly mode: 'authenticated' | 'public' | 'mixed';
22
23
  readonly capabilities?: readonly {
23
24
  readonly kind: string;
@@ -26,12 +26,31 @@ export function validateWebsiteProjection(assistant, surfaces) {
26
26
  });
27
27
  return;
28
28
  }
29
+ if (surface.kind === 'messaging' && !['tool', 'knowledge'].includes(capability.kind)) {
30
+ errors.push({
31
+ code: 'assistant_messaging_unsupported',
32
+ path: `server.assistant.surfaces[${surfaceIndex}].capabilities[${index}]`,
33
+ message: 'Messaging currently supports pure read tools and knowledge only',
34
+ });
35
+ return;
36
+ }
29
37
  if (surface.mode === 'authenticated' || capability.kind !== 'tool')
30
38
  return;
31
39
  const tool = toolsByName.get(capability.name);
32
40
  if (tool === undefined)
33
41
  return;
34
42
  const path = `tools.${tool.name}`;
43
+ if (surface.kind === 'messaging' &&
44
+ (tool.annotations?.readOnlyHint !== true ||
45
+ tool._meta?.ui !== undefined ||
46
+ tool.fulfilment.kind === 'operation' ||
47
+ tool.fulfilment.steps.some((step) => step.kind === 'operation'))) {
48
+ errors.push({
49
+ code: 'assistant_messaging_unsupported',
50
+ path,
51
+ message: `"${tool.name}" requires a write, connector, or UI renderer not enabled for this messaging slice`,
52
+ });
53
+ }
35
54
  if (surface.mode === 'public' && anonymousBehavior(tool) === 'requires-identity') {
36
55
  errors.push({
37
56
  code: 'assistant_public_user_reference',
@@ -0,0 +1,37 @@
1
+ import { MODULE_API_VERSION } from '@noodle-borg/module';
2
+ import { PostgresAuditStore } from './postgres-audit.js';
3
+ import { createAuditEventsRoute } from './route.js';
4
+ export { ensureAuditSchema, insertAuditEvent, PostgresAuditStore } from './postgres-audit.js';
5
+ export { createAuditEventsRoute } from './route.js';
6
+ export function createModule() {
7
+ return {
8
+ name: '@noodle-borg/module-audit',
9
+ version: '0.0.0',
10
+ apiVersion: MODULE_API_VERSION,
11
+ init: async (ctx) => {
12
+ const pool = ctx.stores?.postgresPool?.();
13
+ if (!isPgPool(pool)) {
14
+ throw new Error('@noodle-borg/module-audit requires a Postgres pool');
15
+ }
16
+ const store = new PostgresAuditStore(pool);
17
+ await store.ensureSchema();
18
+ return {
19
+ auditStore: store,
20
+ routes: [createAuditEventsRoute(store)],
21
+ readiness: async () => {
22
+ try {
23
+ await pool.query('SELECT 1');
24
+ return true;
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ },
30
+ };
31
+ },
32
+ };
33
+ }
34
+ function isPgPool(value) {
35
+ return typeof value === 'object' && value !== null && typeof value.query === 'function';
36
+ }
37
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,126 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { AUDIT_SCHEMA_VERSION, redactDetails, } from '@noodle-borg/module';
3
+ /**
4
+ * Create the append-only `audit_events` table if absent (idempotent; run once at startup). Unlike
5
+ * `deploy_records`/`config_values`, this table has **no foreign key to `environments`** — audit evidence
6
+ * must survive tenant/app/env deletion, so it carries plain `org_slug`/`app_slug`/`environment` text and is
7
+ * never cascade-deleted. `seq` (bigserial) gives a stable newest-first order and a future cursor for the
8
+ * audit query API; `details` is `jsonb` holding the already-redacted scalar bag.
9
+ */
10
+ export async function ensureAuditSchema(pool) {
11
+ await pool.query(`
12
+ CREATE TABLE IF NOT EXISTS audit_events (
13
+ seq bigserial PRIMARY KEY,
14
+ id uuid NOT NULL,
15
+ event_type text NOT NULL,
16
+ org_slug text NOT NULL,
17
+ app_slug text,
18
+ environment text,
19
+ deployment_id text,
20
+ actor_subject text,
21
+ actor_email text,
22
+ decision text,
23
+ status text,
24
+ reason_code text,
25
+ schema_version int NOT NULL,
26
+ details jsonb,
27
+ created_at timestamptz NOT NULL
28
+ )
29
+ `);
30
+ await pool.query(`
31
+ CREATE INDEX IF NOT EXISTS audit_events_tenant_time_idx
32
+ ON audit_events (org_slug, app_slug, environment, seq DESC)
33
+ `);
34
+ }
35
+ /**
36
+ * Insert one canonical, redacted audit row through either a pool or an already-open transaction client.
37
+ * This helper deliberately does not manage a transaction: callers using a `PoolClient` keep the audit
38
+ * evidence in the same commit boundary as the state change it records.
39
+ */
40
+ export async function insertAuditEvent(queryable, input, options = {}) {
41
+ const details = redactDetails(input.details);
42
+ await queryable.query(`INSERT INTO audit_events
43
+ (id, event_type, org_slug, app_slug, environment, deployment_id, actor_subject, actor_email,
44
+ decision, status, reason_code, schema_version, details, created_at)
45
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)`, [
46
+ (options.id ?? randomUUID)(),
47
+ input.eventType,
48
+ input.org,
49
+ input.app ?? null,
50
+ input.env ?? null,
51
+ input.deploymentId ?? null,
52
+ input.actorSubject ?? null,
53
+ input.actorEmail ?? null,
54
+ input.decision ?? null,
55
+ input.status !== undefined ? String(input.status) : null,
56
+ input.reasonCode ?? null,
57
+ AUDIT_SCHEMA_VERSION,
58
+ details !== undefined ? JSON.stringify(details) : null,
59
+ (options.now ?? (() => new Date()))(),
60
+ ]);
61
+ }
62
+ /**
63
+ * Relational system of record for {@link AuditEvent}s (parallels {@link InMemoryAuditStore}). The `pg.Pool`
64
+ * is injected; this class is pure SQL and unit-testable against any local Postgres.
65
+ */
66
+ export class PostgresAuditStore {
67
+ #pool;
68
+ #now;
69
+ #id;
70
+ constructor(pool, options = {}) {
71
+ this.#pool = pool;
72
+ this.#now = options.now ?? (() => new Date());
73
+ this.#id = options.id ?? randomUUID;
74
+ }
75
+ ensureSchema() {
76
+ return ensureAuditSchema(this.#pool);
77
+ }
78
+ async emit(input) {
79
+ await insertAuditEvent(this.#pool, input, { now: this.#now, id: this.#id });
80
+ }
81
+ async list(filter) {
82
+ const org = validateSlug('org', filter.org);
83
+ const clauses = ['org_slug = $1'];
84
+ const values = [org];
85
+ if (filter.app !== undefined) {
86
+ values.push(filter.app);
87
+ clauses.push(`app_slug = $${values.length}`);
88
+ }
89
+ if (filter.env !== undefined) {
90
+ values.push(filter.env);
91
+ clauses.push(`environment = $${values.length}`);
92
+ }
93
+ if (filter.eventType !== undefined) {
94
+ values.push(filter.eventType);
95
+ clauses.push(`event_type = $${values.length}`);
96
+ }
97
+ const limit = filter.limit !== undefined ? ` LIMIT ${Number(filter.limit)}` : '';
98
+ const { rows } = await this.#pool.query(`SELECT * FROM audit_events WHERE ${clauses.join(' AND ')} ORDER BY seq DESC${limit}`, values);
99
+ return rows.map(rowToEvent);
100
+ }
101
+ }
102
+ function rowToEvent(row) {
103
+ return {
104
+ id: row.id,
105
+ schemaVersion: row.schema_version,
106
+ eventType: row.event_type,
107
+ org: row.org_slug,
108
+ createdAt: row.created_at.toISOString(),
109
+ ...(row.app_slug !== null ? { app: row.app_slug } : {}),
110
+ ...(row.environment !== null ? { env: row.environment } : {}),
111
+ ...(row.deployment_id !== null ? { deploymentId: row.deployment_id } : {}),
112
+ ...(row.actor_subject !== null ? { actorSubject: row.actor_subject } : {}),
113
+ ...(row.actor_email !== null ? { actorEmail: row.actor_email } : {}),
114
+ ...(row.decision !== null ? { decision: row.decision } : {}),
115
+ ...(row.status !== null ? { status: row.status } : {}),
116
+ ...(row.reason_code !== null ? { reasonCode: row.reason_code } : {}),
117
+ ...(row.details !== null ? { details: row.details } : {}),
118
+ };
119
+ }
120
+ function validateSlug(label, value) {
121
+ if (!/^[a-z0-9][a-z0-9-]{0,62}$/.test(value)) {
122
+ throw new Error(`${label} must be a lowercase slug`);
123
+ }
124
+ return value;
125
+ }
126
+ //# sourceMappingURL=postgres-audit.js.map