@noodleseed/one 0.169.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.
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/capabilities.d.ts +19 -0
- package/dist/commands/capabilities.d.ts.map +1 -0
- package/dist/commands/capabilities.js +128 -0
- package/dist/commands/capabilities.js.map +1 -0
- package/dist/commands/catalog.d.ts.map +1 -1
- package/dist/commands/catalog.js +2 -1
- package/dist/commands/catalog.js.map +1 -1
- package/dist/commands/whatsapp-ops.d.ts +13 -0
- package/dist/commands/whatsapp-ops.d.ts.map +1 -0
- package/dist/commands/whatsapp-ops.js +244 -0
- package/dist/commands/whatsapp-ops.js.map +1 -0
- package/dist/commands/whatsapp-output.d.ts +3 -0
- package/dist/commands/whatsapp-output.d.ts.map +1 -0
- package/dist/commands/whatsapp-output.js +70 -0
- package/dist/commands/whatsapp-output.js.map +1 -0
- package/dist/dev.js +2 -0
- package/dist/dev.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/plugin-mode/plugin-operation-contract.d.ts +3 -3
- package/node_modules/@noodle-borg/agent-kit/dist/curated/command-groups.js +8 -0
- package/node_modules/@noodle-borg/agent-kit/dist/curated/error-fixes.js +1 -0
- package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +2 -2
- package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js +5 -0
- package/node_modules/@noodle-borg/agent-kit/dist/skill-authoring-refs.js +3 -2
- package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +6 -0
- package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
- package/node_modules/@noodle-borg/app-audit/dist/audit-embedded-assistant.js +2 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-configuration.js +2 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-interactive.d.ts +1 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-interactive.js +4 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-model-context.js +9 -4
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-turn-context.d.ts +16 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-turn-context.js +19 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-coordinator.d.ts +145 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-coordinator.js +378 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-inbox.js +160 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-provider-blocks.d.ts +18 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-provider-blocks.js +69 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-spend.js +82 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-store.d.ts +42 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-store.js +76 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-types.d.ts +134 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-types.js +72 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-work.d.ts +20 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-work.js +221 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/messaging-tool-dispatch.d.ts +15 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/messaging-tool-dispatch.js +34 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/model-request.d.ts +19 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/model-request.js +22 -6
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.d.ts +7 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.js +7 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-assistant-appearance-store.js +83 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-assistant-elevation.js +151 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-assistant-interaction-writes.js +45 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-assistant-interactions.js +340 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-assistant.js +439 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-channel-store.js +102 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-continuity-store.js +159 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-elevation-store.js +110 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-embed-store.js +134 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/postgres.js +11 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-surface.d.ts +8 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-surface.js +23 -0
- package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +19 -4
- package/node_modules/@noodle-borg/authoring/dist/assistant.js +51 -20
- package/node_modules/@noodle-borg/authoring/dist/index.d.ts +2 -1
- package/node_modules/@noodle-borg/authoring/dist/index.js +2 -1
- package/node_modules/@noodle-borg/authoring/dist/load-authored.js +1 -1
- package/node_modules/@noodle-borg/authoring/dist/recording.d.ts +2 -1
- package/node_modules/@noodle-borg/authoring/dist/recording.js +29 -1
- package/node_modules/@noodle-borg/authoring/dist/server.d.ts +3 -0
- package/node_modules/@noodle-borg/authoring/dist/server.js +5 -1
- package/node_modules/@noodle-borg/authoring/dist/web-capabilities.d.ts +17 -0
- package/node_modules/@noodle-borg/authoring/dist/web-capabilities.js +15 -0
- package/node_modules/@noodle-borg/authoring/package.json +1 -0
- package/node_modules/@noodle-borg/capabilities/dist/index.js +7 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-channels.js +121 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-hosted-observability.js +102 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/index.js +1 -0
- package/node_modules/@noodle-borg/compiler/dist/artifact/assemble.js +3 -0
- package/node_modules/@noodle-borg/compiler/dist/artifact/config.js +1 -1
- package/node_modules/@noodle-borg/compiler/dist/artifact/types.d.ts +2 -0
- package/node_modules/@noodle-borg/compiler/dist/artifact/version.d.ts +2 -1
- package/node_modules/@noodle-borg/compiler/dist/artifact/version.js +2 -1
- package/node_modules/@noodle-borg/compiler/dist/compile.js +2 -0
- package/node_modules/@noodle-borg/compiler/dist/errors.d.ts +1 -1
- package/node_modules/@noodle-borg/compiler/dist/fulfilment-emit.js +18 -3
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +106 -12
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +60 -29
- package/node_modules/@noodle-borg/compiler/dist/manifest/website-projection.d.ts +1 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/website-projection.js +19 -0
- package/node_modules/@noodle-borg/compiler/dist/web-capabilities.js +55 -0
- package/node_modules/@noodle-borg/compiler/package.json +1 -0
- package/node_modules/@noodle-borg/connector-http/dist/ssrf.js +5 -0
- package/node_modules/@noodle-borg/knowledge-crawl/dist/bounded-body.js +38 -0
- package/node_modules/@noodle-borg/knowledge-crawl/dist/first-party.js +2 -3
- package/node_modules/@noodle-borg/knowledge-crawl/dist/html-text.js +10 -2
- package/node_modules/@noodle-borg/knowledge-crawl/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/knowledge-crawl/dist/index.js +1 -0
- package/node_modules/@noodle-borg/knowledge-crawl/dist/page-reader.d.ts +26 -0
- package/node_modules/@noodle-borg/knowledge-crawl/dist/page-reader.js +127 -0
- package/node_modules/@noodle-borg/knowledge-crawl/dist/robots.js +48 -23
- package/node_modules/@noodle-borg/knowledge-crawl/package.json +1 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/budget.d.ts +29 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/budget.js +124 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/contracts.d.ts +212 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/contracts.js +113 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/errors.d.ts +8 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/errors.js +21 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/executor.d.ts +26 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/executor.js +156 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/limits.d.ts +15 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/limits.js +15 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/operator-contracts.d.ts +159 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/operator-contracts.js +69 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/operator-route.d.ts +5 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/operator-route.js +160 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/policy-store.d.ts +34 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/policy-store.js +61 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/portable.d.ts +10 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/portable.js +10 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/service.d.ts +83 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/service.js +139 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/urls.d.ts +3 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/urls.js +30 -0
- package/node_modules/@noodle-borg/managed-capabilities/package.json +49 -0
- package/node_modules/@noodle-borg/module/dist/contract.d.ts +11 -0
- package/node_modules/@noodle-borg/module-audit/dist/index.js +37 -0
- package/node_modules/@noodle-borg/module-audit/dist/postgres-audit.js +126 -0
- package/node_modules/@noodle-borg/module-audit/dist/route.js +66 -0
- package/node_modules/@noodle-borg/runtime/dist/confirmation.js +29 -0
- package/node_modules/@noodle-borg/runtime/dist/connector/types.d.ts +2 -0
- package/node_modules/@noodle-borg/runtime/dist/execute.d.ts +3 -0
- package/node_modules/@noodle-borg/runtime/dist/execute.js +6 -1
- package/node_modules/@noodle-borg/runtime/dist/index.d.ts +2 -1
- package/node_modules/@noodle-borg/runtime/dist/index.js +2 -1
- package/node_modules/@noodle-borg/runtime/dist/interactive.js +10 -1
- package/node_modules/@noodle-borg/runtime/dist/managed-origins.js +8 -4
- package/node_modules/@noodle-borg/runtime/dist/operation-execution.js +3 -0
- package/node_modules/@noodle-borg/runtime/dist/postgres-state-handles.js +311 -0
- package/node_modules/@noodle-borg/runtime/dist/postgres.js +4 -0
- package/node_modules/@noodle-borg/runtime/dist/result-meta.d.ts +2 -0
- package/node_modules/@noodle-borg/runtime/dist/result-meta.js +6 -0
- package/node_modules/@noodle-borg/runtime/dist/result.d.ts +4 -0
- package/node_modules/@noodle-borg/runtime/dist/web-capabilities.d.ts +71 -0
- package/node_modules/@noodle-borg/runtime/dist/web-capabilities.js +41 -0
- package/node_modules/@noodle-borg/runtime/package.json +1 -0
- package/node_modules/@noodle-borg/service/dist/assistant-composition.js +18 -0
- package/node_modules/@noodle-borg/service/dist/build-info.js +5 -2
- package/node_modules/@noodle-borg/service/dist/business-information/source-ingestion-coordinator.js +7 -0
- package/node_modules/@noodle-borg/service/dist/channels/360dialog.js +233 -0
- package/node_modules/@noodle-borg/service/dist/channels/cipher.js +37 -0
- package/node_modules/@noodle-borg/service/dist/channels/composition.js +37 -0
- package/node_modules/@noodle-borg/service/dist/channels/inference-guard.js +53 -0
- package/node_modules/@noodle-borg/service/dist/channels/runtime.js +337 -0
- package/node_modules/@noodle-borg/service/dist/channels/worker-loop.js +50 -0
- package/node_modules/@noodle-borg/service/dist/local-options.d.ts +1 -0
- package/node_modules/@noodle-borg/service/dist/local-web-capabilities.js +13 -0
- package/node_modules/@noodle-borg/service/dist/mcp-protocol-runtime.js +21 -0
- package/node_modules/@noodle-borg/service/dist/modules/route-dispatch.js +13 -0
- package/node_modules/@noodle-borg/service/dist/registry-compile.js +3 -0
- package/node_modules/@noodle-borg/service/dist/registry-types.d.ts +1 -0
- package/node_modules/@noodle-borg/service/dist/registry.js +3 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +66 -35
- package/node_modules/@noodle-borg/service/dist/routes/assistant-interactions.js +7 -4
- package/node_modules/@noodle-borg/service/dist/routes/assistant-model-binding.js +6 -2
- package/node_modules/@noodle-borg/service/dist/routes/business-information-channels.js +14 -1
- package/node_modules/@noodle-borg/service/dist/routes/whatsapp.js +238 -0
- package/node_modules/@noodle-borg/service/dist/serve-local.js +14 -0
- package/node_modules/@noodle-borg/service/dist/serve-resource-auth.js +10 -0
- package/node_modules/@noodle-borg/service/dist/service.js +37 -33
- package/node_modules/@noodle-borg/service/dist/web-capabilities.js +39 -0
- package/node_modules/@noodle-borg/service/package.json +3 -1
- 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 +9 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +2 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/index.js +2 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/service-info.d.ts +2 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/service-info.js +10 -2
- package/node_modules/@noodle-borg/wire-contracts/dist/whatsapp.d.ts +426 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/whatsapp.js +213 -0
- package/node_modules/@noodle-borg/wire-contracts/package.json +1 -0
- package/package.json +3 -2
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { beginChannelProviderBlock, CHANNEL_RETENTION_MS, ChannelCoordinator, ChannelError, channelDigest, channelProviderBlocks, finishChannelProviderBlock, messagingSurfaceOf, projectArtifactForSurface, resolveInvocationContextSnapshot, withAssistantTurnExecutionAuthority, } from '@noodle-borg/assistant-gateway/portable';
|
|
2
|
+
import { runAgentTurn } from '../routes/assistant-agent.js';
|
|
3
|
+
import { resolveAssistantKnowledge } from '../routes/assistant-knowledge.js';
|
|
4
|
+
import { resolveAssistantModelBinding } from '../routes/assistant-model-binding.js';
|
|
5
|
+
import { activeAssistantTarget } from '../routes/assistant-session-target.js';
|
|
6
|
+
import { resolveConfigScope } from '../store.js';
|
|
7
|
+
import { Dialog360, verifyWhatsAppWebhookSecret, WHATSAPP_CALLBACK_HEADER } from './360dialog.js';
|
|
8
|
+
import { verifiedInferenceBound, withChannelInferenceGuard } from './inference-guard.js';
|
|
9
|
+
export class WhatsAppRuntime {
|
|
10
|
+
options;
|
|
11
|
+
deps;
|
|
12
|
+
channels;
|
|
13
|
+
cursor;
|
|
14
|
+
constructor(options, deps) {
|
|
15
|
+
this.options = options;
|
|
16
|
+
this.deps = deps;
|
|
17
|
+
this.channels = new ChannelCoordinator(options.store, () => deps.clock?.().getTime() ?? Date.now());
|
|
18
|
+
options.worker.attach(() => this.sweep());
|
|
19
|
+
}
|
|
20
|
+
async provider(binding) {
|
|
21
|
+
const values = await this.deps.registry.configStore.resolveConfigValues('secret', resolveConfigScope(binding.tenant));
|
|
22
|
+
const apiKey = values[binding.apiKeySecret], webhookSecret = values[binding.webhookSecret];
|
|
23
|
+
if (!apiKey ||
|
|
24
|
+
!webhookSecret ||
|
|
25
|
+
apiKey === webhookSecret ||
|
|
26
|
+
!verifyWhatsAppWebhookSecret(webhookSecret, webhookSecret))
|
|
27
|
+
throw new ChannelError('channel_credentials_missing');
|
|
28
|
+
await this.channels.checkCredentials(binding.id, channelDigest(JSON.stringify([apiKey, webhookSecret])));
|
|
29
|
+
return { adapter: new Dialog360(apiKey, this.options.providerFetch), webhookSecret };
|
|
30
|
+
}
|
|
31
|
+
webhookUrl(req, binding) {
|
|
32
|
+
return `${this.deps.serviceBase(req).replace(/\/$/, '')}/v1/channels/whatsapp/webhooks/${binding.id}`;
|
|
33
|
+
}
|
|
34
|
+
async target(binding) {
|
|
35
|
+
const target = await activeAssistantTarget(this.deps, binding.tenant);
|
|
36
|
+
if (!target || target.deploymentId !== binding.deploymentId)
|
|
37
|
+
throw new ChannelError('deployment_changed');
|
|
38
|
+
const surface = messagingSurfaceOf(target.served.artifact.server.assistant);
|
|
39
|
+
if (!surface)
|
|
40
|
+
throw new ChannelError('messaging_surface_missing');
|
|
41
|
+
if (binding.capabilities.some((cap) => !surface.capabilities.some((authored) => authored.kind === cap.kind && authored.name === cap.name)))
|
|
42
|
+
throw new ChannelError('capability_not_authored');
|
|
43
|
+
const artifact = projectArtifactForSurface(target.served.artifact, binding.capabilities);
|
|
44
|
+
if (artifact.tools.some((tool) => tool.annotations?.readOnlyHint !== true || tool._meta?.ui))
|
|
45
|
+
throw new ChannelError('messaging_action_unsupported');
|
|
46
|
+
// This first slice has no separately budgeted connector-backed inference. Pure reads and
|
|
47
|
+
// the existing corpus search remain available; external lookups require their owning slice.
|
|
48
|
+
const external = (fulfilment) => fulfilment.kind === 'operation' || fulfilment.steps.some((step) => step.kind === 'operation');
|
|
49
|
+
if (artifact.tools.some((tool) => external(tool.fulfilment)) ||
|
|
50
|
+
(artifact.server.context?.ambient && external(artifact.server.context.ambient.fulfilment)))
|
|
51
|
+
throw new ChannelError('external_lookup_not_enabled');
|
|
52
|
+
return { ...target, served: { ...target.served, artifact } };
|
|
53
|
+
}
|
|
54
|
+
async doctor(id, req) {
|
|
55
|
+
const binding = await this.channels.internal(id);
|
|
56
|
+
const checks = [];
|
|
57
|
+
const check = async (name, work) => {
|
|
58
|
+
try {
|
|
59
|
+
await work();
|
|
60
|
+
checks.push({ name, status: 'ready' });
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
checks.push({
|
|
64
|
+
name,
|
|
65
|
+
status: 'unavailable',
|
|
66
|
+
code: error instanceof ChannelError ? error.code : 'check_unavailable',
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
await check('durability', async () => {
|
|
71
|
+
if (!this.options.store.durable)
|
|
72
|
+
throw new ChannelError('durable_storage_required');
|
|
73
|
+
});
|
|
74
|
+
await check('worker', async () => {
|
|
75
|
+
if (!this.options.worker.ready())
|
|
76
|
+
throw new ChannelError('worker_unavailable');
|
|
77
|
+
});
|
|
78
|
+
await check('deployment', async () => {
|
|
79
|
+
await this.target(binding);
|
|
80
|
+
});
|
|
81
|
+
await check('model_cost', async () => {
|
|
82
|
+
const model = await resolveAssistantModelBinding(await this.target(binding), binding.tenant, binding.deploymentId, this.deps, 'whatsapp');
|
|
83
|
+
if (!model)
|
|
84
|
+
throw new ChannelError('model_not_configured');
|
|
85
|
+
verifiedInferenceBound(model, this.channels.now());
|
|
86
|
+
});
|
|
87
|
+
await check('history_policy', async () => {
|
|
88
|
+
await this.retention(binding);
|
|
89
|
+
});
|
|
90
|
+
await check('knowledge', async () => {
|
|
91
|
+
const target = await this.target(binding);
|
|
92
|
+
if ((target.served.artifact.server.knowledge?.length ?? 0) > 0 &&
|
|
93
|
+
!(await resolveAssistantKnowledge(target.served)))
|
|
94
|
+
throw new ChannelError('knowledge_unavailable');
|
|
95
|
+
});
|
|
96
|
+
await check('provider_asset', async () => {
|
|
97
|
+
const health = await (await this.provider(binding)).adapter.health();
|
|
98
|
+
if (health.phoneNumberId !== binding.phoneNumberId)
|
|
99
|
+
throw new ChannelError('asset_mismatch');
|
|
100
|
+
if (!health.canSend)
|
|
101
|
+
throw new ChannelError('provider_messaging_unavailable');
|
|
102
|
+
});
|
|
103
|
+
await check('webhook', async () => {
|
|
104
|
+
const state = await this.webhook(id, req);
|
|
105
|
+
if (!state.matches || !state.authenticated)
|
|
106
|
+
throw new ChannelError('webhook_not_configured');
|
|
107
|
+
});
|
|
108
|
+
const ready = checks.every((check) => check.status === 'ready');
|
|
109
|
+
if (ready)
|
|
110
|
+
await this.channels.markReady(id, binding.revision);
|
|
111
|
+
return { ready, revision: binding.revision, checks, webhookUrl: this.webhookUrl(req, binding) };
|
|
112
|
+
}
|
|
113
|
+
async webhook(id, req) {
|
|
114
|
+
const binding = await this.channels.internal(id);
|
|
115
|
+
const { adapter, webhookSecret } = await this.provider(binding);
|
|
116
|
+
const current = await adapter.webhook();
|
|
117
|
+
return {
|
|
118
|
+
url: this.webhookUrl(req, binding),
|
|
119
|
+
matches: current.url === this.webhookUrl(req, binding),
|
|
120
|
+
authenticated: verifyWhatsAppWebhookSecret(current.headers?.[WHATSAPP_CALLBACK_HEADER], webhookSecret),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
async configureWebhook(id, req) {
|
|
124
|
+
const binding = await this.channels.internal(id);
|
|
125
|
+
if (binding.state !== 'paused')
|
|
126
|
+
throw new ChannelError('pause_before_webhook_change');
|
|
127
|
+
const { adapter, webhookSecret } = await this.provider(binding);
|
|
128
|
+
const existing = await adapter.webhook();
|
|
129
|
+
const url = this.webhookUrl(req, binding);
|
|
130
|
+
if (existing.url && existing.url !== url)
|
|
131
|
+
throw new ChannelError('webhook_ownership_conflict');
|
|
132
|
+
if (existing.url !== url ||
|
|
133
|
+
!verifyWhatsAppWebhookSecret(existing.headers?.[WHATSAPP_CALLBACK_HEADER], webhookSecret))
|
|
134
|
+
await adapter.configureWebhook(url, webhookSecret);
|
|
135
|
+
}
|
|
136
|
+
async projection(tenant) {
|
|
137
|
+
const binding = await this.channels.get(tenant);
|
|
138
|
+
if (!binding)
|
|
139
|
+
return {
|
|
140
|
+
status: 'unavailable',
|
|
141
|
+
capabilities: [],
|
|
142
|
+
reason: 'WhatsApp is not configured.',
|
|
143
|
+
};
|
|
144
|
+
const view = {
|
|
145
|
+
bindingId: binding.id,
|
|
146
|
+
revision: binding.revision,
|
|
147
|
+
capabilities: binding.capabilities.map((cap) => `${cap.kind}:${cap.name}`),
|
|
148
|
+
};
|
|
149
|
+
if (binding.state !== 'enabled')
|
|
150
|
+
return { ...view, status: 'paused' };
|
|
151
|
+
try {
|
|
152
|
+
await this.target(await this.channels.internal(binding.id));
|
|
153
|
+
return { ...view, status: 'ready' };
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return {
|
|
157
|
+
...view,
|
|
158
|
+
status: 'unavailable',
|
|
159
|
+
reason: 'The active deployment or installation is unavailable.',
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async providerBlock(id, participantId, blocked, actor, key, protectedPhone) {
|
|
164
|
+
const operation = await beginChannelProviderBlock(this.channels.store, id, participantId, blocked ? 'blocked' : 'unblocked', actor, key, this.channels.now(), protectedPhone);
|
|
165
|
+
if (!operation)
|
|
166
|
+
return;
|
|
167
|
+
try {
|
|
168
|
+
const binding = await this.channels.internal(id);
|
|
169
|
+
if (binding.generation !== operation.generation)
|
|
170
|
+
throw new ChannelError('authority_changed');
|
|
171
|
+
const { adapter } = await this.provider(binding);
|
|
172
|
+
const result = await adapter.setBlocked(operation.address.value, blocked);
|
|
173
|
+
await finishChannelProviderBlock(this.channels.store, id, participantId, operation.revision, result.state, this.channels.now(), result.code);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
await finishChannelProviderBlock(this.channels.store, id, participantId, operation.revision, 'unknown', this.channels.now(), 'provider_outcome_unknown');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async blocks(id, after) {
|
|
180
|
+
const local = await this.channels.blocks(id, after);
|
|
181
|
+
const provider = await channelProviderBlocks(this.channels.store, id, this.channels.now(), after);
|
|
182
|
+
const ids = [
|
|
183
|
+
...new Set([
|
|
184
|
+
...local.map((item) => item.participantId),
|
|
185
|
+
...provider.map((item) => item.participantId),
|
|
186
|
+
]),
|
|
187
|
+
];
|
|
188
|
+
const data = ids
|
|
189
|
+
.sort()
|
|
190
|
+
.slice(0, 100)
|
|
191
|
+
.map((participantId) => {
|
|
192
|
+
const block = local.find((item) => item.participantId === participantId);
|
|
193
|
+
const match = provider.find((item) => item.participantId === participantId);
|
|
194
|
+
return {
|
|
195
|
+
...(block ?? {
|
|
196
|
+
participantId,
|
|
197
|
+
actor: match.actor,
|
|
198
|
+
reason: 'operator',
|
|
199
|
+
until: null,
|
|
200
|
+
}),
|
|
201
|
+
local: !!block && (block.until === null || block.until > this.channels.now()),
|
|
202
|
+
...(match
|
|
203
|
+
? {
|
|
204
|
+
provider: {
|
|
205
|
+
desired: match.desired,
|
|
206
|
+
state: match.state,
|
|
207
|
+
updatedAt: match.updatedAt,
|
|
208
|
+
...(match.code ? { code: match.code } : {}),
|
|
209
|
+
},
|
|
210
|
+
}
|
|
211
|
+
: {}),
|
|
212
|
+
};
|
|
213
|
+
});
|
|
214
|
+
return { data, ...(data.length === 100 ? { next: data.at(-1).participantId } : {}) };
|
|
215
|
+
}
|
|
216
|
+
async retention(binding) {
|
|
217
|
+
const days = (await this.options.historyDays?.(binding.tenant)) ?? 7;
|
|
218
|
+
if (!Number.isInteger(days) || days < 1)
|
|
219
|
+
throw new ChannelError('history_policy_unavailable');
|
|
220
|
+
return Math.min(CHANNEL_RETENTION_MS, days * 86_400_000);
|
|
221
|
+
}
|
|
222
|
+
async sweep() {
|
|
223
|
+
const page = await this.channels.bindingPage(this.cursor);
|
|
224
|
+
this.cursor = page.after;
|
|
225
|
+
// Bounded parallelism and rotating registry pages prevent a busy tenant starving later ones.
|
|
226
|
+
for (let offset = 0; offset < page.ids.length; offset += 5) {
|
|
227
|
+
await Promise.all(page.ids.slice(offset, offset + 5).map((id) => this.process(id)));
|
|
228
|
+
}
|
|
229
|
+
await this.options.store.prune(this.channels.now(), 1000);
|
|
230
|
+
}
|
|
231
|
+
async process(id) {
|
|
232
|
+
try {
|
|
233
|
+
if (this.options.worker.signal.aborted)
|
|
234
|
+
return;
|
|
235
|
+
const binding = await this.channels.internal(id);
|
|
236
|
+
const retention = await this.retention(binding);
|
|
237
|
+
await this.channels.expireHistory(id, retention);
|
|
238
|
+
const work = await this.channels.claim(id, retention);
|
|
239
|
+
if (work)
|
|
240
|
+
await this.answer(work);
|
|
241
|
+
for (let n = 0; n < 5 && !this.options.worker.signal.aborted; n++) {
|
|
242
|
+
const send = await this.channels.prepareSend(id);
|
|
243
|
+
if (!send)
|
|
244
|
+
break;
|
|
245
|
+
await this.target(await this.channels.internal(id));
|
|
246
|
+
const { adapter } = await this.provider(send.binding);
|
|
247
|
+
await this.channels.assertSend(id, send.event.id, send.event.lease);
|
|
248
|
+
this.options.worker.signal.throwIfAborted();
|
|
249
|
+
await this.channels.sent(id, send.event.id, send.event.lease, await adapter.send({ to: send.participant.address, text: send.event.reply }));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
this.deps.logger?.warn('assistant.channel.worker', { code: 'channel_work_failed' });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
async answer(work) {
|
|
257
|
+
const { binding, participant, event } = work;
|
|
258
|
+
const abort = new AbortController();
|
|
259
|
+
const signal = AbortSignal.any([
|
|
260
|
+
abort.signal,
|
|
261
|
+
this.options.worker.signal,
|
|
262
|
+
AbortSignal.timeout(60_000),
|
|
263
|
+
]);
|
|
264
|
+
const historyRetention = await this.retention(binding);
|
|
265
|
+
const beforeStep = async () => {
|
|
266
|
+
if ((await this.retention(binding)) !== historyRetention)
|
|
267
|
+
throw new ChannelError('history_policy_changed');
|
|
268
|
+
signal.throwIfAborted();
|
|
269
|
+
await this.provider(await this.channels.internal(binding.id));
|
|
270
|
+
await this.channels.renew(binding.id, event.id, event.lease);
|
|
271
|
+
await this.target(await this.channels.internal(binding.id));
|
|
272
|
+
};
|
|
273
|
+
const timer = setInterval(() => {
|
|
274
|
+
void beforeStep().catch(() => abort.abort());
|
|
275
|
+
}, 10_000);
|
|
276
|
+
timer.unref?.();
|
|
277
|
+
try {
|
|
278
|
+
const target = await this.target(binding);
|
|
279
|
+
const model = await resolveAssistantModelBinding(target, binding.tenant, binding.deploymentId, this.deps, 'whatsapp');
|
|
280
|
+
if (!model)
|
|
281
|
+
throw new ChannelError('model_not_configured');
|
|
282
|
+
const session = {
|
|
283
|
+
kind: 'messaging',
|
|
284
|
+
channel: 'whatsapp',
|
|
285
|
+
id: event.id,
|
|
286
|
+
bindingId: binding.id,
|
|
287
|
+
participantId: participant.id,
|
|
288
|
+
tenant: binding.tenant,
|
|
289
|
+
deploymentId: binding.deploymentId,
|
|
290
|
+
caller: { identityKind: 'anonymous', subject: participant.id, roles: [], scopes: [] },
|
|
291
|
+
history: participant.history.map(({ role, content }) => ({ role, content })),
|
|
292
|
+
modelToolUses: participant.modelToolUses ?? [],
|
|
293
|
+
};
|
|
294
|
+
const context = await resolveInvocationContextSnapshot({
|
|
295
|
+
artifact: target.served.artifact,
|
|
296
|
+
executeDeps: withAssistantTurnExecutionAuthority({ ...target.served.deps, signal }, target.served.artifact, session),
|
|
297
|
+
caller: session.caller,
|
|
298
|
+
instant: new Date(this.channels.now()),
|
|
299
|
+
});
|
|
300
|
+
let text = '', failure;
|
|
301
|
+
await runAgentTurn(target, session, event.text, context, this.deps, (result) => {
|
|
302
|
+
if (result.event === 'content' && typeof result.data.delta === 'string')
|
|
303
|
+
text += result.data.delta;
|
|
304
|
+
if (result.event === 'error')
|
|
305
|
+
failure = String(result.data.code ?? 'answer_failed');
|
|
306
|
+
}, undefined, undefined, undefined, false, {
|
|
307
|
+
binding: withChannelInferenceGuard(model, this.channels, binding.id, event.id, beforeStep),
|
|
308
|
+
beforeStep,
|
|
309
|
+
claimTool: (name) => this.channels.claimTool(binding.id, participant.id, name),
|
|
310
|
+
signal,
|
|
311
|
+
});
|
|
312
|
+
if (failure || !text.trim())
|
|
313
|
+
throw new ChannelError(failure ?? 'answer_unavailable');
|
|
314
|
+
const disclosure = participant.history.length === 0
|
|
315
|
+
? `I’m ${target.served.artifact.server.branding?.name ?? target.served.artifact.server.title}’s AI assistant.\n\n`
|
|
316
|
+
: '';
|
|
317
|
+
const answer = disclosure + text.trim();
|
|
318
|
+
const bounded = answer.length <= 3500
|
|
319
|
+
? answer
|
|
320
|
+
: `${answer.slice(0, 3350).replace(/[\uD800-\uDBFF]$/u, '')}\n\nAsk me to expand on any part.`;
|
|
321
|
+
await this.channels.complete(binding.id, event.id, event.lease, bounded, historyRetention);
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
const code = error instanceof ChannelError ? error.code : 'answer_failed';
|
|
325
|
+
try {
|
|
326
|
+
await this.channels.complete(binding.id, event.id, event.lease, `I couldn’t complete that request. For help, contact ${binding.supportEmail}.`, historyRetention, code);
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
await this.channels.fail(binding.id, event.id, event.lease, code);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
finally {
|
|
333
|
+
clearInterval(timer);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
//# sourceMappingURL=runtime.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** A process scheduler, never the owner of acknowledged work. PostgreSQL leases own recovery. */
|
|
2
|
+
export class ChannelWorkerLoop {
|
|
3
|
+
onError;
|
|
4
|
+
run;
|
|
5
|
+
timer;
|
|
6
|
+
running;
|
|
7
|
+
stopped = true;
|
|
8
|
+
tickAt = 0;
|
|
9
|
+
abort = new AbortController();
|
|
10
|
+
get signal() {
|
|
11
|
+
return this.abort.signal;
|
|
12
|
+
}
|
|
13
|
+
constructor(onError = () => undefined) {
|
|
14
|
+
this.onError = onError;
|
|
15
|
+
}
|
|
16
|
+
attach(run) {
|
|
17
|
+
this.run = run;
|
|
18
|
+
}
|
|
19
|
+
start() {
|
|
20
|
+
if (this.timer)
|
|
21
|
+
return;
|
|
22
|
+
this.stopped = false;
|
|
23
|
+
this.abort = new AbortController();
|
|
24
|
+
this.timer = setInterval(() => this.wake(), 1000);
|
|
25
|
+
this.timer.unref?.();
|
|
26
|
+
this.wake();
|
|
27
|
+
}
|
|
28
|
+
ready() {
|
|
29
|
+
return !this.stopped && !!this.run && Date.now() - this.tickAt < 90_000;
|
|
30
|
+
}
|
|
31
|
+
wake() {
|
|
32
|
+
if (this.stopped || this.running || !this.run)
|
|
33
|
+
return;
|
|
34
|
+
this.tickAt = Date.now();
|
|
35
|
+
this.running = this.run()
|
|
36
|
+
.catch(() => this.onError('channel_worker_failed'))
|
|
37
|
+
.finally(() => {
|
|
38
|
+
this.running = undefined;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
async stop() {
|
|
42
|
+
this.stopped = true;
|
|
43
|
+
this.abort.abort();
|
|
44
|
+
if (this.timer)
|
|
45
|
+
clearInterval(this.timer);
|
|
46
|
+
this.timer = undefined;
|
|
47
|
+
await this.running;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=worker-loop.js.map
|
|
@@ -6,6 +6,7 @@ import type { LocalDevtoolsDelegatedExchangeRuntime } from './local-devtools-del
|
|
|
6
6
|
import type { ServerRegistry } from './registry.js';
|
|
7
7
|
import type { ConfigStore, TenantBridgeAuthConfig } from './store.js';
|
|
8
8
|
type LocalRuntimeOptions = {
|
|
9
|
+
readonly capabilities?: import('@noodle-borg/managed-capabilities').CapabilityService;
|
|
9
10
|
readonly assistantStore?: import('@noodle-borg/assistant-gateway/portable').AssistantStore;
|
|
10
11
|
readonly assistantAppearance?: import('@noodle-borg/assistant-gateway/portable').AssistantAppearanceSettingsStore;
|
|
11
12
|
readonly publicEmbeds?: import('@noodle-borg/assistant-gateway/portable').PublicEmbedStore;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { InMemoryDailyCounterStore } from '@noodle-borg/admission-limits/portable';
|
|
2
|
+
import { PublicPageReader } from '@noodle-borg/knowledge-crawl';
|
|
3
|
+
import { CapabilityService, InMemoryCapabilityPolicyStore, } from '@noodle-borg/managed-capabilities';
|
|
4
|
+
/** Loopback-only composition. Keep PostgreSQL imports out of the published CLI graph. */
|
|
5
|
+
export function localWebCapabilities() {
|
|
6
|
+
return new CapabilityService({
|
|
7
|
+
profile: 'development',
|
|
8
|
+
policies: new InMemoryCapabilityPolicyStore(),
|
|
9
|
+
counters: new InMemoryDailyCounterStore(),
|
|
10
|
+
reader: new PublicPageReader(),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=local-web-capabilities.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { RequestStateManager, requestStateSecretBox, } from '@noodle-borg/protocol';
|
|
2
|
+
/**
|
|
3
|
+
* Build the fleet-shared request-state manager. Static-key deployments use an HKDF domain-separated key;
|
|
4
|
+
* KMS deployments reuse the configured envelope-encryption custodian because its raw KEK cannot be
|
|
5
|
+
* exported for HKDF.
|
|
6
|
+
*/
|
|
7
|
+
export function createHostedMcpRequestStateManager(input) {
|
|
8
|
+
if (input.secretMasterKey !== undefined) {
|
|
9
|
+
return new RequestStateManager(requestStateSecretBox(Buffer.from(input.secretMasterKey, 'base64')));
|
|
10
|
+
}
|
|
11
|
+
return input.secretBox === undefined ? undefined : new RequestStateManager(input.secretBox);
|
|
12
|
+
}
|
|
13
|
+
/** Resolve the temporary origin-wide rollout gate from operator configuration. */
|
|
14
|
+
export function resolveMcpProtocolMode(value) {
|
|
15
|
+
if (value === undefined || value === 'dual')
|
|
16
|
+
return 'dual';
|
|
17
|
+
if (value === 'legacy-only')
|
|
18
|
+
return value;
|
|
19
|
+
throw new Error('NOODLE_MCP_PROTOCOL_MODE must be dual or legacy-only');
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=mcp-protocol-runtime.js.map
|
|
@@ -14,6 +14,19 @@ export function createModuleRouteContext(hostRequest, deps) {
|
|
|
14
14
|
logger: deps.logger ?? noopModuleLogger,
|
|
15
15
|
controlPlane: deps.gate,
|
|
16
16
|
audit: deps.audit,
|
|
17
|
+
capabilityDeployments: {
|
|
18
|
+
get: async (request, scope) => {
|
|
19
|
+
if (request !== hostRequest || !authorizedOrgs.has(scope.org))
|
|
20
|
+
return undefined;
|
|
21
|
+
const target = await deps.registry.getActiveByTenant(scope);
|
|
22
|
+
if (target?.deploymentId === undefined)
|
|
23
|
+
return undefined;
|
|
24
|
+
return {
|
|
25
|
+
deploymentId: target.deploymentId,
|
|
26
|
+
declarations: target.served.artifact.server.capabilities ?? [],
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
},
|
|
17
30
|
platformIdentityRecovery: {
|
|
18
31
|
reconcile: (plan) => deps.registry.reconcilePlatformAccountReset(plan),
|
|
19
32
|
customerAuthRestoreProjections: async (deploymentIds) => (await deps.registry.customerAuthRestoreProjections(deploymentIds)).map((projection) => ({
|
|
@@ -14,6 +14,7 @@ import { missingCapabilityErrors, missingSecretErrors, missingVariableErrors, }
|
|
|
14
14
|
import { servedTargetFor } from './registry-targets.js';
|
|
15
15
|
import { createDeploymentStateConnector, } from './state-connector-factory.js';
|
|
16
16
|
import { resolveConfigScope, } from './store.js';
|
|
17
|
+
import { deploymentWebConnector } from './web-capabilities.js';
|
|
17
18
|
/** Reject unsupported persisted semantics before constructing executable runtime dependencies. */
|
|
18
19
|
export async function compilePersistedRegistryRecord(record, compileTarget) {
|
|
19
20
|
const versionError = deploymentRecordVersionError(record);
|
|
@@ -197,11 +198,13 @@ export async function compileRegistryTarget(context, input) {
|
|
|
197
198
|
artifact,
|
|
198
199
|
...(boundDeploymentId === undefined ? {} : { deploymentId: boundDeploymentId }),
|
|
199
200
|
});
|
|
201
|
+
const webConnector = deploymentWebConnector(artifact, tenant, boundDeploymentId, context.capabilities);
|
|
200
202
|
return {
|
|
201
203
|
artifact,
|
|
202
204
|
deps: {
|
|
203
205
|
connectors: new InMemoryConnectorRegistry([
|
|
204
206
|
...context.platformConnectors,
|
|
207
|
+
...(webConnector === undefined ? [] : [webConnector]),
|
|
205
208
|
...(nativeRecords === undefined ? [] : [nativeRecords]),
|
|
206
209
|
...(stateConnector !== undefined ? [stateConnector] : []),
|
|
207
210
|
...httpConnectors,
|
|
@@ -96,6 +96,7 @@ export interface AccessUpdateOptions {
|
|
|
96
96
|
readonly serverVersion?: string | undefined;
|
|
97
97
|
}
|
|
98
98
|
export interface ServerRegistryOptions {
|
|
99
|
+
readonly capabilities?: import('@noodle-borg/managed-capabilities').CapabilityService;
|
|
99
100
|
readonly nativeRecords?: NativeRecordConnectorFactory;
|
|
100
101
|
/** Pure renderer injection used for deployment-bound package construction and failure testing. */
|
|
101
102
|
readonly renderAppPackage?: AppPackageRenderer;
|
|
@@ -32,6 +32,7 @@ export class ServerRegistry {
|
|
|
32
32
|
#platformCatalog;
|
|
33
33
|
#platformConnectors;
|
|
34
34
|
#nativeRecords;
|
|
35
|
+
#capabilities;
|
|
35
36
|
#stateHandleStoreFactory;
|
|
36
37
|
#delegatedCredentialStore;
|
|
37
38
|
#sealCustomerCredential;
|
|
@@ -61,6 +62,7 @@ export class ServerRegistry {
|
|
|
61
62
|
this.#platformCatalog = withBuiltinStateCatalog(options.platformCatalog ?? []);
|
|
62
63
|
this.#platformConnectors = options.platformConnectors ?? [];
|
|
63
64
|
this.#nativeRecords = options.nativeRecords;
|
|
65
|
+
this.#capabilities = options.capabilities;
|
|
64
66
|
this.#stateHandleStoreFactory = options.stateHandleStoreFactory;
|
|
65
67
|
this.#delegatedCredentialStore = options.delegatedCredentialStore;
|
|
66
68
|
this.#sealCustomerCredential = options.sealCustomerCredential;
|
|
@@ -311,6 +313,7 @@ export class ServerRegistry {
|
|
|
311
313
|
stateHandleStoreFactory: this.#stateHandleStoreFactory,
|
|
312
314
|
platformConnectors: this.#platformConnectors,
|
|
313
315
|
nativeRecords: this.#nativeRecords,
|
|
316
|
+
capabilities: this.#capabilities,
|
|
314
317
|
policyGate: this.#policyGate,
|
|
315
318
|
appPackageRenderer: this.#appPackageRenderer,
|
|
316
319
|
knowledgeSearch: this.#knowledgeSearch,
|