@noodleseed/one 0.187.0 → 0.189.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/commands/solutions-conversations.js +1 -1
- package/dist/commands/solutions-conversations.js.map +1 -1
- package/dist/commands/solutions-history.d.ts.map +1 -1
- package/dist/commands/solutions-history.js +5 -2
- package/dist/commands/solutions-history.js.map +1 -1
- package/dist/commands/whatsapp-ops.d.ts +1 -1
- package/dist/commands/whatsapp-ops.d.ts.map +1 -1
- package/dist/commands/whatsapp-ops.js +31 -9
- package/dist/commands/whatsapp-ops.js.map +1 -1
- package/dist/commands/whatsapp-output.d.ts +1 -1
- package/dist/commands/whatsapp-output.d.ts.map +1 -1
- package/dist/commands/whatsapp-output.js +8 -6
- package/dist/commands/whatsapp-output.js.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/behavior-skills.js +19 -35
- package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +2 -2
- package/node_modules/@noodle-borg/agent-kit/dist/index.d.ts +0 -1
- package/node_modules/@noodle-borg/agent-kit/dist/index.js +9 -27
- package/node_modules/@noodle-borg/agent-kit/dist/plugin-bootstrap-skill.js +1 -10
- package/node_modules/@noodle-borg/agent-kit/dist/skill-authoring-split.js +68 -0
- package/node_modules/@noodle-borg/agent-kit/dist/skill-content.js +38 -4
- package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +7 -5
- package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-split.js +68 -0
- package/node_modules/@noodle-borg/agent-kit/dist/skill-existing-app-ref.js +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/skill-router.d.ts +3 -2
- package/node_modules/@noodle-borg/agent-kit/dist/skill-router.js +11 -99
- package/node_modules/@noodle-borg/agent-kit/dist/skill-server-playbook-ref.js +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/skill-verification-ref.js +2 -2
- package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-model-context.js +1 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-coordinator.d.ts +89 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-coordinator.js +20 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-inbox.js +16 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-store.d.ts +1 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-types.d.ts +34 -3
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-types.js +10 -4
- package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +1 -1
- package/node_modules/@noodle-borg/authoring/dist/assistant.js +1 -1
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-channels.js +26 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-conversations.js +1 -1
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-history.js +1 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +4 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +1 -1
- 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 +11 -2
- package/node_modules/@noodle-borg/runtime/dist/connector/types.d.ts +1 -1
- package/node_modules/@noodle-borg/service/dist/application-history-settings.js +2 -0
- package/node_modules/@noodle-borg/service/dist/build-info.js +1 -0
- package/node_modules/@noodle-borg/service/dist/channels/composition.js +3 -2
- package/node_modules/@noodle-borg/service/dist/channels/messaging-eligibility.js +6 -1
- package/node_modules/@noodle-borg/service/dist/channels/reply-bound.js +4 -0
- package/node_modules/@noodle-borg/service/dist/channels/runtime.js +38 -10
- package/node_modules/@noodle-borg/service/dist/channels/twilio-sms.js +188 -0
- package/node_modules/@noodle-borg/service/dist/channels/twilio-webhook.js +16 -0
- package/node_modules/@noodle-borg/service/dist/conversation-history/contracts.js +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/business-information-channels.js +3 -1
- package/node_modules/@noodle-borg/service/dist/routes/service-probes.js +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/sms-twilio.js +66 -0
- package/node_modules/@noodle-borg/service/dist/routes/whatsapp.js +31 -21
- package/node_modules/@noodle-borg/service/dist/service.js +2 -2
- package/node_modules/@noodle-borg/service/package.json +1 -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 +3 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/application-history-settings.d.ts +7 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/application-history-settings.js +2 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/conversation-history.d.ts +12 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/conversation-history.js +1 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/index.js +1 -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 +2 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/sms.d.ts +176 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/sms.js +41 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/whatsapp.d.ts +10 -2
- package/node_modules/@noodle-borg/wire-contracts/dist/whatsapp.js +2 -1
- package/package.json +2 -2
- package/node_modules/@noodle-borg/agent-kit/dist/plan-execution-skill.d.ts +0 -10
- package/node_modules/@noodle-borg/agent-kit/dist/plan-execution-skill.js +0 -56
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { renderEmbeddedAssistantReference } from './skill-embedded-assistant-ref.js';
|
|
2
|
+
const TOPIC_BY_HEADING = {
|
|
3
|
+
'Public messaging: WhatsApp, Instagram DMs and SMS': 'messaging',
|
|
4
|
+
'Collect interactions': 'messaging',
|
|
5
|
+
Architecture: 'core',
|
|
6
|
+
'Select the architecture before code': 'core',
|
|
7
|
+
'Install into the existing application': 'core',
|
|
8
|
+
'Author and validate': 'core',
|
|
9
|
+
'Product workflow guidance': 'core',
|
|
10
|
+
'Customize the presentation': 'rendering',
|
|
11
|
+
'Configure and deploy': 'operations',
|
|
12
|
+
'Access modes and customer auth': 'identity',
|
|
13
|
+
'Create the backend client': 'identity',
|
|
14
|
+
'Integrate the customer backend': 'identity',
|
|
15
|
+
'Ground time and ambient facts': 'identity',
|
|
16
|
+
'Structured missing input': 'identity',
|
|
17
|
+
'Verified session context (identity and claims)': 'identity',
|
|
18
|
+
'The session response': 'identity',
|
|
19
|
+
'Mount a public website surface': 'rendering',
|
|
20
|
+
'Choose a browser renderer': 'rendering',
|
|
21
|
+
'Host readiness and promotion': 'operations',
|
|
22
|
+
'Launch and qualified-usage proof': 'operations',
|
|
23
|
+
'Toolchain requirements': 'operations',
|
|
24
|
+
'Verify the boundary': 'operations',
|
|
25
|
+
'Troubleshooting: symptom to diagnosis': 'operations',
|
|
26
|
+
};
|
|
27
|
+
/** Keep the authored reference single-sourced while loading only the requested topic in an agent. */
|
|
28
|
+
export function renderEmbeddedAssistantTopic(topic) {
|
|
29
|
+
const source = renderEmbeddedAssistantReference();
|
|
30
|
+
const sections = source.split(/(?=^## )/m);
|
|
31
|
+
const selected = sections.slice(1).filter((section) => {
|
|
32
|
+
const heading = /^## ([^\n]+)/.exec(section)?.[1];
|
|
33
|
+
if (heading === undefined)
|
|
34
|
+
throw new Error('embedded assistant reference has no heading');
|
|
35
|
+
if (heading === 'Contents')
|
|
36
|
+
return false;
|
|
37
|
+
if (!(heading in TOPIC_BY_HEADING)) {
|
|
38
|
+
throw new Error(`unrouted embedded assistant topic: ${heading}`);
|
|
39
|
+
}
|
|
40
|
+
return TOPIC_BY_HEADING[heading] === topic;
|
|
41
|
+
});
|
|
42
|
+
if (selected.length === 0)
|
|
43
|
+
throw new Error(`empty embedded assistant topic: ${topic}`);
|
|
44
|
+
const contents = selected.map((section) => {
|
|
45
|
+
const heading = /^## ([^\n]+)/.exec(section)?.[1];
|
|
46
|
+
if (heading === undefined)
|
|
47
|
+
throw new Error('embedded assistant section has no heading');
|
|
48
|
+
return `- ${heading}`;
|
|
49
|
+
});
|
|
50
|
+
const intro = topic === 'core'
|
|
51
|
+
? [
|
|
52
|
+
'# Embedded assistant',
|
|
53
|
+
'',
|
|
54
|
+
'Read this overview for architecture and authoring. Load only the topic needed next:',
|
|
55
|
+
'- [Messaging and collection](./embedded-assistant-messaging.md)',
|
|
56
|
+
'- [Identity and backend sessions](./embedded-assistant-identity.md)',
|
|
57
|
+
'- [Appearance and browser rendering](./embedded-assistant-rendering.md)',
|
|
58
|
+
'- [Deployment, verification and recovery](./embedded-assistant-operations.md)',
|
|
59
|
+
'',
|
|
60
|
+
'## Contents',
|
|
61
|
+
'',
|
|
62
|
+
...contents,
|
|
63
|
+
'',
|
|
64
|
+
].join('\n')
|
|
65
|
+
: `# Embedded assistant: ${topic}\n\n## Contents\n\n${contents.join('\n')}\n\n`;
|
|
66
|
+
return `${intro}${selected.join('').trimEnd()}\n`;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=skill-embedded-assistant-split.js.map
|
|
@@ -42,7 +42,7 @@ export function renderWrapExistingAppReference() {
|
|
|
42
42
|
'6. **Reject unstable seams.** Do not call an unstable server-call protocol whose identifier or serialization changes across builds. Never use direct database access, including ORM or SQL; it bypasses application authorization and business rules. Do not log in and copy a browser session into the runtime; copying a browser session or its credentials into the runtime is prohibited. Do not treat a service credential as per-user authorization. Propose a narrow application-owned HTTP handler instead.',
|
|
43
43
|
'7. **Separate implementation ownership.** Produce an existing-application phase for handlers/auth/tests, a Noodle-project phase for TypeScript connector/tools/fakes/tests, and a cross-surface verification phase. If repositories differ, create separate repository-scoped plans and approval boundaries.',
|
|
44
44
|
'8. **Present and stop.** Show the capability map, identity/routing decision, minimum honest application changes, rejected alternatives, test matrix, risks, and phases in the conversation. A design approval is review only. Local implementation requires an explicit “proceed” for the named repository. Local implementation approval does not authorize hosted configuration, live writes, deployment, publication, or issue-state changes; those require their own authority.',
|
|
45
|
-
'9. **
|
|
45
|
+
'9. **Continue approved work.** After the user authorizes the implementation, follow the repository workflow and the relevant Noodle authoring or API-connection skill. Verify real output without reopening settled decisions.',
|
|
46
46
|
'',
|
|
47
47
|
'## Verification evidence',
|
|
48
48
|
'',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { BehaviorSkillName } from './behavior-skills.js';
|
|
2
2
|
import type { AgentTarget } from './index.js';
|
|
3
|
-
export declare const SKILL_DESCRIPTION
|
|
3
|
+
export declare const SKILL_DESCRIPTION = "Build or operate a Noodle Seed MCP server or app with TypeScript and the noodle CLI. Use for Noodle project setup, an unclear Noodle workflow, or finding the relevant specialist skill.";
|
|
4
4
|
export type SkillReferencePath = `references/${string}.md`;
|
|
5
5
|
export declare const APP_DIRECTORY_COMPLIANCE_REFERENCE: SkillReferencePath;
|
|
6
6
|
type SkillRouteId = 'design-product' | 'create-product-guide' | 'wrap-existing-app' | 'build-server' | 'connect-api' | 'build-app' | 'verify-recover' | 'debug-recover' | 'inspect-hosted' | 'deploy-hosted' | 'embed-assistant' | 'publish' | 'feedback';
|
|
@@ -11,6 +11,7 @@ export interface SkillRoute {
|
|
|
11
11
|
readonly exitCondition: string;
|
|
12
12
|
}
|
|
13
13
|
export declare const SKILL_ROUTES: readonly SkillRoute[];
|
|
14
|
+
/** A short index for ambiguous requests; installed specialist skills are directly discoverable. */
|
|
14
15
|
export declare function skillRouterBody(_target: AgentTarget): string;
|
|
15
16
|
export {};
|
|
16
17
|
//# sourceMappingURL=skill-router.d.ts.map
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
// Intent router for the project-local `noodle-seed` skill. Keep this file small: it selects one
|
|
2
2
|
// outcome workflow and bounds progressive disclosure. Detailed MCP guidance belongs in the
|
|
3
3
|
// one-level reference tree rendered by skill-content.ts.
|
|
4
|
-
|
|
5
|
-
export const SKILL_DESCRIPTION = 'Use when building, validating, testing, deploying, or operating a local or hosted Noodle Seed ' +
|
|
6
|
-
'MCP server or app authored in TypeScript with the noodle CLI, or improving signup or onboarding ' +
|
|
7
|
-
'conversion or selecting a first Noodle workflow for an account-growth product.';
|
|
4
|
+
export const SKILL_DESCRIPTION = 'Build or operate a Noodle Seed MCP server or app with TypeScript and the noodle CLI. Use for Noodle project setup, an unclear Noodle workflow, or finding the relevant specialist skill.';
|
|
8
5
|
export const APP_DIRECTORY_COMPLIANCE_REFERENCE = 'references/app-directory-compliance.md';
|
|
9
6
|
export const SKILL_ROUTES = [
|
|
10
7
|
{
|
|
11
8
|
id: 'design-product',
|
|
12
|
-
intent: '
|
|
9
|
+
intent: 'Choose a first Noodle workflow or turn a vague MCP idea into a bounded design',
|
|
13
10
|
skill: 'designing-mcp-products',
|
|
14
11
|
exitCondition: 'The product contract identifies the user benefit, model boundary, evidence, and next implementation skill.',
|
|
15
12
|
},
|
|
@@ -86,110 +83,25 @@ export const SKILL_ROUTES = [
|
|
|
86
83
|
exitCondition: 'A sanitized dry-run preview and exact proposal are shown, then one submission occurs only after explicit approval.',
|
|
87
84
|
},
|
|
88
85
|
];
|
|
89
|
-
/**
|
|
90
|
-
const REFERENCE_INDEX_LINES = [
|
|
91
|
-
'- `references/agent-contract.md` — the `--json` envelope, exit codes, and the three output modes.',
|
|
92
|
-
'- `references/product-agent-guides.md` — decide, interview, propose, prove, preview, and explicitly install one host-neutral product guide.',
|
|
93
|
-
'- `references/sdk-surface.md` — what to import from `@noodleseed/one` and which builder to use.',
|
|
94
|
-
'- `references/cli-commands.md` — every `noodle` command, grouped by area.',
|
|
95
|
-
'- `references/compile-errors.md` — fix `noodle validate` errors by code.',
|
|
96
|
-
'- `references/build-an-mcp-server.md` — outcome-first workflow for a focused, tested, model-facing MCP product.',
|
|
97
|
-
'- `references/authoring-workflow.md` — input paths, fit checks, connector patterns, authentication, invocation context, testing, and managed config.',
|
|
98
|
-
'- `references/tool-design.md` — intent-shaped tools, titles and annotations, bounded outputs, tool-surface budget, and context provisioning.',
|
|
99
|
-
'- `references/embedded-assistant.md` — HTTPS origins, managed model config, deploy sequencing, session exchange, browser mounting, and credential boundaries.',
|
|
100
|
-
'- `references/connect-an-api.md` — secure credentials, probe the live API, model the observed shape, and prove real output.',
|
|
101
|
-
'- `references/wrap-existing-app.md` — read-only identity-first planning for an existing application with no stable usable API.',
|
|
102
|
-
'- `references/build-an-mcp-app.md` — product-fit, output-boundary, fallback, and evidence workflow for an MCP App.',
|
|
103
|
-
'- `references/experience-design.md` — design an app experience: funnel boundary, grounding, two users, display modes, and wireframe.',
|
|
104
|
-
'- `references/widgets-and-apps.md` — MCP Apps, typed views, widget hooks, output shaping, and CSP.',
|
|
105
|
-
'- `references/test-in-hosts.md` — connect and test in real MCP hosts and protocol inspection clients.',
|
|
106
|
-
'- `references/verify-and-recover.md` — ordered evidence ladder, bounded repair, and honest completion claims.',
|
|
107
|
-
'- `references/troubleshooting.md` — runtime symptom to cause and fix, locally and hosted.',
|
|
108
|
-
'- `references/inspect-hosted.md` — read-only hosted status, logs, metrics, events, and deployment diagnosis.',
|
|
109
|
-
'- `references/deploy-and-ops.md` — explicitly authorized hosted link, config, deploy, access, connection writes, and rollback.',
|
|
110
|
-
'- `references/publishing.md` — prepare and submit to app and connector directories.',
|
|
111
|
-
`- \`${APP_DIRECTORY_COMPLIANCE_REFERENCE}\` — the pre-submission experience and policy checklist.`,
|
|
112
|
-
'- `references/examples.md` — flagship example index and a canonical `server.ts`.',
|
|
113
|
-
'- `references/feedback.md` — draft sanitized product feedback and submit only with user approval.',
|
|
114
|
-
];
|
|
115
|
-
function routeTableLines() {
|
|
116
|
-
return [
|
|
117
|
-
'| User outcome | Load sibling skill | Canonical playbook | Done when |',
|
|
118
|
-
'| :--- | :--- | :--- | :--- |',
|
|
119
|
-
...SKILL_ROUTES.map((route) => {
|
|
120
|
-
const skill = BEHAVIOR_SKILLS.find((candidate) => candidate.name === route.skill);
|
|
121
|
-
if (skill === undefined)
|
|
122
|
-
throw new Error(`missing behavior skill for route ${route.id}`);
|
|
123
|
-
const supporting = skill.supportingReferences.length > 0
|
|
124
|
-
? skill.supportingReferences.map((reference) => `\`${reference}\``).join(', ')
|
|
125
|
-
: 'None';
|
|
126
|
-
return `| ${route.intent} | \`${route.skill}\` | \`${skill.primaryReference}\` (${supporting}) | ${route.exitCondition} |`;
|
|
127
|
-
}),
|
|
128
|
-
];
|
|
129
|
-
}
|
|
86
|
+
/** A short index for ambiguous requests; installed specialist skills are directly discoverable. */
|
|
130
87
|
export function skillRouterBody(_target) {
|
|
131
|
-
const preamble = 'Use this skill for project-local Noodle Seed authoring in the active coding host; preserve generated and user instruction boundaries.';
|
|
132
88
|
return [
|
|
133
89
|
'# Noodle Seed',
|
|
134
90
|
'',
|
|
135
|
-
'
|
|
136
|
-
'',
|
|
137
|
-
preamble,
|
|
138
|
-
'',
|
|
139
|
-
"If the request is unrelated to the Noodle MCP surface, stop here: follow the project's normal instructions and run no Noodle lifecycle commands.",
|
|
140
|
-
'',
|
|
141
|
-
'**Installed-plugin execution.** When this skill is supplied by the Noodle Developer plugin, you write and test the application source, then use its supported `noodle-readiness` tools for project setup, build gates, exact-target linking, variables, secret-from-environment transfer, gated deployment, and feedback. Treat the corresponding public `noodle ...` command as recovery text, never as permission to discover or expose an internal launcher. If a tool fails, report its structured error and public `noodle ...` command; do not ask the user to copy a private installation path or perform an operation the authorized tool can perform.',
|
|
142
|
-
'',
|
|
143
|
-
'## Route the request',
|
|
144
|
-
'',
|
|
145
|
-
'Choose exactly one primary route from the user outcome below, then load the selected sibling skill and hand off the request. Read that primary reference in full; read supporting references only when the sibling or observed evidence requires them. Do not reread the corpus or restart discovery after the handoff.',
|
|
146
|
-
'',
|
|
147
|
-
'Apply this precedence when wording overlaps: concrete failure evidence takes the debugging route; an explicit request to create, revise, regenerate, or recover an app product skill takes `creating-product-agent-guides`; an MCP App/UI outcome takes the App route; an existing application with no stable, usable API or specification takes `wrapping-existing-applications`; only when all four API-evidence inputs exist—an API base URL, authentication scheme, representative safe read, and observed response—use `connecting-apis-to-mcp`; missing, stale, inaccessible, undocumented-only, or otherwise unusable evidence remains in `wrapping-existing-applications`; both integration routes take precedence over generic server building; hosted inspection is debugging read-only; hosted mutation requires the explicitly requested deployment route.',
|
|
148
|
-
'',
|
|
149
|
-
'Negative routing examples: “Inspect hosted logs/status” → `inspect-hosted` (read-only). “Prepare for deployment” → the applicable build or verification route; with existing access it may inspect `deploy preflight`, then stop with a handoff. Preparation does not authorize `link`, hosted config, publication, rollback, host writes, or submission. “Keep this local” → a build or verification route, never a hosted route.',
|
|
150
|
-
'',
|
|
151
|
-
'## First-workflow heuristic',
|
|
152
|
-
'',
|
|
153
|
-
'When the user asks what to build first or gives only a broad growth goal, inspect the repository and product context before asking them to invent a workflow. Do not classify the business or choose a design from an industry label alone.',
|
|
154
|
-
'',
|
|
155
|
-
'If the evidence shows a public visitor surface, an existing signup or sign-in boundary, a useful pre-account result the assistant can genuinely produce, and a meaningful authenticated outcome inside the product—and more authenticated users are valuable—recommend public-to-product continuous onboarding and route to `embedding-mcp-assistants`. This recommendation is not limited to B2B SaaS. If one of those facts is unclear, ask only for the smallest missing fit fact; if the pattern does not fit, route by the user’s actual outcome instead.',
|
|
156
|
-
'',
|
|
157
|
-
...routeTableLines(),
|
|
158
|
-
'',
|
|
159
|
-
'## Common machine loop',
|
|
160
|
-
'',
|
|
161
|
-
'Every `--json` command speaks the canonical envelope on stdout. Parse machine state instead of scraping human prose; `references/agent-contract.md` owns the envelope, streaming records, and exit codes.',
|
|
162
|
-
'',
|
|
163
|
-
'Inside the installed plugin, perform mapped steps with `noodle-readiness` tools and use the public `noodle ...` spelling only when reporting the logical action or a fallback. Outside the plugin, run the public CLI directly. Never construct a hidden launcher command.',
|
|
164
|
-
'',
|
|
165
|
-
"1. **Discover** — use `noodle commands --json` when the required command or flags are uncertain; don't read CLI source.",
|
|
166
|
-
'2. **Author** — for build routes, edit the configured TypeScript entrypoint, usually `src/server.ts`.',
|
|
167
|
-
'3. **Validate** — run `noodle validate --json`; repair each `error.errors[]` item at its `path`, then re-run `noodle validate --json`.',
|
|
168
|
-
'4. **Smoke** — run `noodle test --json` after validation passes.',
|
|
169
|
-
'5. **Prove the requested level** — connector routes require a safe live read with `noodle tools call`; App routes require `noodle check --json` and `noodle devtools`; hosted or host actions run only when the selected route and current user request authorize that exact level.',
|
|
170
|
-
'6. **Report evidence** — claim only the highest level actually exercised and name anything not run.',
|
|
171
|
-
'',
|
|
172
|
-
'## Reference lookup catalog',
|
|
173
|
-
'',
|
|
174
|
-
'This is a lookup catalog, not a discovery checklist. Return here only when the selected primary route names a missing technical detail:',
|
|
175
|
-
'',
|
|
176
|
-
...REFERENCE_INDEX_LINES,
|
|
91
|
+
'Author Noodle Seed MCP servers and apps in TypeScript. The compiler owns manifests and runtime artifacts. If the request is unrelated to this Noodle project, follow the project instructions without running Noodle lifecycle commands.',
|
|
177
92
|
'',
|
|
178
|
-
'##
|
|
93
|
+
'## Find the relevant skill',
|
|
179
94
|
'',
|
|
180
|
-
'
|
|
95
|
+
'Use a specialist skill directly when the task is clear. For an ambiguous Noodle request, use this index; a task may need more than one skill as work progresses.',
|
|
96
|
+
...SKILL_ROUTES.map((route) => `- ${route.intent}: \`${route.skill}\``),
|
|
181
97
|
'',
|
|
182
|
-
'
|
|
98
|
+
'For a concrete failure, start with `debugging-mcp-delivery`. For hosted status or logs, inspect read-only. Deployment, configuration writes, rollback, and directory submission require an authorized target and action. API connection needs a usable API origin, auth scheme, safe read, and observed result; when those are missing, inspect the application and establish them before promising a live integration.',
|
|
183
99
|
'',
|
|
184
|
-
'
|
|
185
|
-
'- Never expose an internal launcher or private installation path, ask the user to paste a command the plugin can execute, or use an ad hoc shell/file-parsing pipeline to move a secret. Use the typed secret-from-environment tool or `noodle secrets set ... --from-env NAME`.',
|
|
186
|
-
'- Do not hand-author manifest JSON/YAML, runtime artifacts, connector IR, or hosted asset metadata.',
|
|
187
|
-
'- Do not add static data-plane credential paths; hosted access is identity-based.',
|
|
188
|
-
'- Hosted mutation is opt-in. Run `link`, hosted secret/variable/config/access changes, deploy, rollback, host configuration writes, or directory submission only when the current user request explicitly authorizes the exact mutation and target. An inspect, prepare, validate, test, or local-only request grants no such authority; stop and ask before crossing that boundary.',
|
|
100
|
+
'## Find current product facts',
|
|
189
101
|
'',
|
|
190
|
-
'
|
|
102
|
+
'Use [the SDK reference](./references/sdk-surface.md) for TypeScript builders, [the CLI reference](./references/cli-commands.md) or `noodle commands --json` for commands, and [the machine response contract](./references/agent-contract.md) when parsing errors. Read only the reference relevant to the task. Validate authored changes with `noodle validate --json`, then run the relevant local behavior check. A connector-backed capability also needs a representative safe live read before claiming its response mapping works.',
|
|
191
103
|
'',
|
|
192
|
-
'
|
|
104
|
+
'In the installed Noodle Developer plugin, use supported `noodle-readiness` tools for lifecycle operations; report public `noodle ...` commands for recovery. Outside the plugin, run the public CLI. Keep credentials and internal launcher paths out of prompts and output. Transfer secrets only through supported from-environment operations. Hosted changes need the user-authorized target and action; local checks do not prove hosted behavior.',
|
|
193
105
|
].join('\n');
|
|
194
106
|
}
|
|
195
107
|
//# sourceMappingURL=skill-router.js.map
|
|
@@ -50,7 +50,7 @@ export function renderBuildAnMcpServerReference() {
|
|
|
50
50
|
'- Tool registers but returns empty or `undefined` fields: inspect one sanitized real response, correct `${response...}` mappings, and rerun the same read.',
|
|
51
51
|
'- Credential unavailable: verify `secret(...)` naming and the effective local target; never inline or print the secret.',
|
|
52
52
|
'- Missing product input: ask for the smallest concrete example, schema, or rule that unblocks the selected job. Do not widen the build to compensate.',
|
|
53
|
-
'- Repeated failure at the same layer:
|
|
53
|
+
'- Repeated failure at the same layer: continue only with a new evidence-backed cause; otherwise preserve the sanitized error and passing evidence, then report the blocker and exact next action.',
|
|
54
54
|
'',
|
|
55
55
|
'## Stop conditions',
|
|
56
56
|
'',
|
|
@@ -42,7 +42,7 @@ export function renderVerifyAndRecoverReference() {
|
|
|
42
42
|
'3. Form one evidence-backed cause from the observed output. If the two canonical supports do not cover it, select one matching symptom from the router lookup catalog; do not scan every recovery path.',
|
|
43
43
|
'4. Make the smallest in-scope repair. Do not freeform re-edit adjacent code, change credentials, redeploy, or add product behavior without evidence and authority.',
|
|
44
44
|
'5. Rerun only the same evidence layer that failed. Once it passes, continue upward only to the user-requested level.',
|
|
45
|
-
'6.
|
|
45
|
+
'6. If the same failure persists without a new evidence-backed cause, preserve the repro and report the blocker. Stop when the next action requires new authority or external state.',
|
|
46
46
|
'',
|
|
47
47
|
'## Verification evidence',
|
|
48
48
|
'',
|
|
@@ -69,7 +69,7 @@ export function renderVerifyAndRecoverReference() {
|
|
|
69
69
|
'',
|
|
70
70
|
'- Stop complete when the user-requested evidence level and every dependency below it pass in the current target.',
|
|
71
71
|
'- Stop blocked when progress requires credentials, approval, host access, deployment authority, production access, or an external-state change not available in scope.',
|
|
72
|
-
'- Stop
|
|
72
|
+
'- Stop when the same failure has no new evidence-backed repair; do not hide repetition behind unrelated edits.',
|
|
73
73
|
'- Never claim fixed or working without rerunning the failed layer, and never upgrade compile, local, deployment, or stale historical evidence into a stronger claim.',
|
|
74
74
|
].join('\n');
|
|
75
75
|
}
|
|
@@ -107,6 +107,7 @@ function invocationContextMessages(context) {
|
|
|
107
107
|
}
|
|
108
108
|
/** What each messaging app renders; Instagram caps a reply at 1000 bytes, so the model aims well under. */
|
|
109
109
|
const MESSAGING_PROFILES = {
|
|
110
|
+
sms: 'SMS surface: concise plain text, no Markdown, at most 300 characters. Use bare URLs and answer one question at a time.',
|
|
110
111
|
whatsapp: 'WhatsApp surface: concise plain text, at most 3500 characters.',
|
|
111
112
|
instagram: 'Instagram DM surface: plain text only, no Markdown or lists, at most 600 characters, bare URLs.',
|
|
112
113
|
};
|
|
@@ -36,6 +36,27 @@ export declare class ChannelCoordinator {
|
|
|
36
36
|
actor: string;
|
|
37
37
|
readyRevision?: number | undefined;
|
|
38
38
|
readyAt?: number | undefined;
|
|
39
|
+
} | {
|
|
40
|
+
channel: "sms";
|
|
41
|
+
provider: "twilio";
|
|
42
|
+
accountSid: string;
|
|
43
|
+
phoneNumberSid: string;
|
|
44
|
+
phoneNumber: string;
|
|
45
|
+
id: string;
|
|
46
|
+
tenant: TenantRef;
|
|
47
|
+
apiKeySecret: string;
|
|
48
|
+
deploymentId: string;
|
|
49
|
+
capabilities: readonly import("./channel-types.js").ChannelCapability[];
|
|
50
|
+
supportEmail: string;
|
|
51
|
+
revision: number;
|
|
52
|
+
generation: number;
|
|
53
|
+
state: "paused" | "enabled" | "disconnected";
|
|
54
|
+
limits: import("./channel-types.js").ChannelLimits;
|
|
55
|
+
createdAt: number;
|
|
56
|
+
updatedAt: number;
|
|
57
|
+
actor: string;
|
|
58
|
+
readyRevision?: number | undefined;
|
|
59
|
+
readyAt?: number | undefined;
|
|
39
60
|
} | {
|
|
40
61
|
provider: import("./channel-types.js").ChannelProvider;
|
|
41
62
|
phoneNumberId: string;
|
|
@@ -84,6 +105,27 @@ export declare class ChannelCoordinator {
|
|
|
84
105
|
actor: string;
|
|
85
106
|
readyRevision?: number | undefined;
|
|
86
107
|
readyAt?: number | undefined;
|
|
108
|
+
} | {
|
|
109
|
+
channel: "sms";
|
|
110
|
+
provider: "twilio";
|
|
111
|
+
accountSid: string;
|
|
112
|
+
phoneNumberSid: string;
|
|
113
|
+
phoneNumber: string;
|
|
114
|
+
id: string;
|
|
115
|
+
tenant: TenantRef;
|
|
116
|
+
apiKeySecret: string;
|
|
117
|
+
deploymentId: string;
|
|
118
|
+
capabilities: readonly import("./channel-types.js").ChannelCapability[];
|
|
119
|
+
supportEmail: string;
|
|
120
|
+
revision: number;
|
|
121
|
+
generation: number;
|
|
122
|
+
state: "paused" | "enabled" | "disconnected";
|
|
123
|
+
limits: import("./channel-types.js").ChannelLimits;
|
|
124
|
+
createdAt: number;
|
|
125
|
+
updatedAt: number;
|
|
126
|
+
actor: string;
|
|
127
|
+
readyRevision?: number | undefined;
|
|
128
|
+
readyAt?: number | undefined;
|
|
87
129
|
} | {
|
|
88
130
|
provider: import("./channel-types.js").ChannelProvider;
|
|
89
131
|
phoneNumberId: string;
|
|
@@ -127,6 +169,27 @@ export declare class ChannelCoordinator {
|
|
|
127
169
|
actor: string;
|
|
128
170
|
readyRevision?: number | undefined;
|
|
129
171
|
readyAt?: number | undefined;
|
|
172
|
+
} | {
|
|
173
|
+
channel: "sms";
|
|
174
|
+
provider: "twilio";
|
|
175
|
+
accountSid: string;
|
|
176
|
+
phoneNumberSid: string;
|
|
177
|
+
phoneNumber: string;
|
|
178
|
+
id: string;
|
|
179
|
+
tenant: TenantRef;
|
|
180
|
+
apiKeySecret: string;
|
|
181
|
+
deploymentId: string;
|
|
182
|
+
capabilities: readonly import("./channel-types.js").ChannelCapability[];
|
|
183
|
+
supportEmail: string;
|
|
184
|
+
revision: number;
|
|
185
|
+
generation: number;
|
|
186
|
+
state: "paused" | "enabled" | "disconnected";
|
|
187
|
+
limits: import("./channel-types.js").ChannelLimits;
|
|
188
|
+
createdAt: number;
|
|
189
|
+
updatedAt: number;
|
|
190
|
+
actor: string;
|
|
191
|
+
readyRevision?: number | undefined;
|
|
192
|
+
readyAt?: number | undefined;
|
|
130
193
|
} | {
|
|
131
194
|
provider: import("./channel-types.js").ChannelProvider;
|
|
132
195
|
phoneNumberId: string;
|
|
@@ -151,6 +214,11 @@ export declare class ChannelCoordinator {
|
|
|
151
214
|
block(id: string, participantId: string, actor: string, key: string, until: number | null): Promise<void>;
|
|
152
215
|
unblock(id: string, participantId: string, actor: string, key: string): Promise<void>;
|
|
153
216
|
blocks(id: string, after?: string): Promise<ChannelBlock[]>;
|
|
217
|
+
optouts(id: string, after?: string): Promise<{
|
|
218
|
+
at: number;
|
|
219
|
+
blocked: boolean;
|
|
220
|
+
participantId: string;
|
|
221
|
+
}[]>;
|
|
154
222
|
events(id: string, after?: string): Promise<ChannelEvent[]>;
|
|
155
223
|
forget(id: string, participantId: string, actor: string, key: string): Promise<void>;
|
|
156
224
|
updateLimits(id: string, input: ChannelLimitsInput, actor: string, key: string, revision: number): Promise<{
|
|
@@ -173,6 +241,27 @@ export declare class ChannelCoordinator {
|
|
|
173
241
|
actor: string;
|
|
174
242
|
readyRevision?: number | undefined;
|
|
175
243
|
readyAt?: number | undefined;
|
|
244
|
+
} | {
|
|
245
|
+
channel: "sms";
|
|
246
|
+
provider: "twilio";
|
|
247
|
+
accountSid: string;
|
|
248
|
+
phoneNumberSid: string;
|
|
249
|
+
phoneNumber: string;
|
|
250
|
+
id: string;
|
|
251
|
+
tenant: TenantRef;
|
|
252
|
+
apiKeySecret: string;
|
|
253
|
+
deploymentId: string;
|
|
254
|
+
capabilities: readonly import("./channel-types.js").ChannelCapability[];
|
|
255
|
+
supportEmail: string;
|
|
256
|
+
revision: number;
|
|
257
|
+
generation: number;
|
|
258
|
+
state: "paused" | "enabled" | "disconnected";
|
|
259
|
+
limits: import("./channel-types.js").ChannelLimits;
|
|
260
|
+
createdAt: number;
|
|
261
|
+
updatedAt: number;
|
|
262
|
+
actor: string;
|
|
263
|
+
readyRevision?: number | undefined;
|
|
264
|
+
readyAt?: number | undefined;
|
|
176
265
|
} | {
|
|
177
266
|
provider: import("./channel-types.js").ChannelProvider;
|
|
178
267
|
phoneNumberId: string;
|
|
@@ -11,7 +11,8 @@ export async function requireChannel(tx, id) {
|
|
|
11
11
|
}
|
|
12
12
|
export async function channelBlocked(tx, id, participant, now) {
|
|
13
13
|
const block = await channelValue(tx, id, `block:${participant}`);
|
|
14
|
-
|
|
14
|
+
const optout = await channelValue(tx, id, `optout:${participant}`);
|
|
15
|
+
return optout?.blocked === true || (!!block && (block.until === null || block.until > now));
|
|
15
16
|
}
|
|
16
17
|
export async function requireChannelLive(tx, id) {
|
|
17
18
|
const binding = await requireChannel(tx, id);
|
|
@@ -38,6 +39,8 @@ async function mutation(tx, id, key, input, now, work) {
|
|
|
38
39
|
}
|
|
39
40
|
/** Validates a channel's provider fields and names the asset its binding will own. */
|
|
40
41
|
function configuredAsset(input) {
|
|
42
|
+
if (input.channel === 'sms')
|
|
43
|
+
return channelAssetKey('sms', input.phoneNumber);
|
|
41
44
|
if (input.channel === 'instagram')
|
|
42
45
|
return channelAssetKey('instagram', input.instagramAccountId);
|
|
43
46
|
const meta = input.provider === 'meta';
|
|
@@ -47,6 +50,8 @@ function configuredAsset(input) {
|
|
|
47
50
|
return channelAssetKey('whatsapp', input.phoneNumberId);
|
|
48
51
|
}
|
|
49
52
|
function configuredBinding(input, id, state) {
|
|
53
|
+
if (input.channel === 'sms')
|
|
54
|
+
return { ...input, id, provider: 'twilio', ...state };
|
|
50
55
|
if (input.channel === 'instagram')
|
|
51
56
|
return { ...input, id, provider: 'meta', ...state };
|
|
52
57
|
const { provider, channel: _whatsapp, ...configured } = input;
|
|
@@ -216,6 +221,20 @@ export class ChannelCoordinator {
|
|
|
216
221
|
})).map((row) => row.value);
|
|
217
222
|
});
|
|
218
223
|
}
|
|
224
|
+
async optouts(id, after) {
|
|
225
|
+
return this.store.transaction([id], async (tx) => {
|
|
226
|
+
await requireChannel(tx, id);
|
|
227
|
+
const rows = await tx.list(id, {
|
|
228
|
+
kind: 'optout',
|
|
229
|
+
limit: 100,
|
|
230
|
+
...(after ? { after: `optout:${after}` } : {}),
|
|
231
|
+
});
|
|
232
|
+
return rows.map((row) => ({
|
|
233
|
+
participantId: row.id.slice('optout:'.length),
|
|
234
|
+
...row.value,
|
|
235
|
+
}));
|
|
236
|
+
});
|
|
237
|
+
}
|
|
219
238
|
async events(id, after) {
|
|
220
239
|
return this.store.transaction([id], async (tx) => {
|
|
221
240
|
await requireChannel(tx, id);
|
|
@@ -63,7 +63,22 @@ export async function receiveChannelMessages(store, id, messages, now, retention
|
|
|
63
63
|
if ((await channelCounter(tx, id, 'ingress', now, CHANNEL_DAY_MS)) >= 10_000)
|
|
64
64
|
throw new ChannelError('ingress_capacity');
|
|
65
65
|
const participantId = channelParticipantId(binding, message.address);
|
|
66
|
-
|
|
66
|
+
let code = await refusal(tx, binding, message, participantId, pending, now);
|
|
67
|
+
if (binding.channel === 'sms' &&
|
|
68
|
+
message.control &&
|
|
69
|
+
message.eventAt >= now - CHANNEL_RETENTION_MS &&
|
|
70
|
+
message.eventAt <= now + 60_000) {
|
|
71
|
+
if (message.control !== 'help') {
|
|
72
|
+
const key = `optout:${participantId}`;
|
|
73
|
+
const prior = await channelValue(tx, id, key);
|
|
74
|
+
// Older callbacks cannot undo a newer preference; STOP wins equal-second timestamps.
|
|
75
|
+
if (!prior ||
|
|
76
|
+
message.eventAt > prior.at ||
|
|
77
|
+
(message.eventAt === prior.at && message.control === 'stop'))
|
|
78
|
+
await tx.put(id, channelRow(key, 'optout', { at: message.eventAt, blocked: message.control === 'stop' }, now));
|
|
79
|
+
}
|
|
80
|
+
code = `provider_${message.control}`;
|
|
81
|
+
}
|
|
67
82
|
const sequence = ((await channelValue(tx, id, 'sequence')) ?? 0) + 1;
|
|
68
83
|
await tx.put(id, channelRow('sequence', 'counter', sequence, now));
|
|
69
84
|
const eventId = `e_${String(sequence).padStart(20, '0')}`;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Private channel journal. Each binding is a transaction/lease authority, not a browser session. */
|
|
2
2
|
export interface ChannelRow {
|
|
3
3
|
readonly id: string;
|
|
4
|
-
readonly kind: 'binding' | 'asset' | 'participant' | 'event' | 'counter' | 'spend' | 'block' | 'mutation' | 'interaction';
|
|
4
|
+
readonly kind: 'binding' | 'asset' | 'participant' | 'event' | 'counter' | 'spend' | 'block' | 'optout' | 'mutation' | 'interaction';
|
|
5
5
|
readonly state?: string;
|
|
6
6
|
readonly updatedAt: number;
|
|
7
7
|
readonly expiresAt?: number;
|
|
@@ -28,7 +28,7 @@ export type ChannelCapability = {
|
|
|
28
28
|
readonly name: string;
|
|
29
29
|
};
|
|
30
30
|
/** One binding per channel per environment; each channel owns its own provider asset. */
|
|
31
|
-
export declare const CHANNEL_KINDS: readonly ["whatsapp", "instagram"];
|
|
31
|
+
export declare const CHANNEL_KINDS: readonly ["whatsapp", "instagram", "sms"];
|
|
32
32
|
export type ChannelKind = (typeof CHANNEL_KINDS)[number];
|
|
33
33
|
export interface ChannelBindingBase {
|
|
34
34
|
readonly id: string;
|
|
@@ -68,7 +68,14 @@ export interface InstagramBinding extends ChannelBindingBase {
|
|
|
68
68
|
readonly instagramAccountId: string;
|
|
69
69
|
readonly pageId: string;
|
|
70
70
|
}
|
|
71
|
-
export
|
|
71
|
+
export interface SmsBinding extends ChannelBindingBase {
|
|
72
|
+
readonly channel: 'sms';
|
|
73
|
+
readonly provider: 'twilio';
|
|
74
|
+
readonly accountSid: string;
|
|
75
|
+
readonly phoneNumberSid: string;
|
|
76
|
+
readonly phoneNumber: string;
|
|
77
|
+
}
|
|
78
|
+
export type ChannelBinding = WhatsAppBinding | InstagramBinding | SmsBinding;
|
|
72
79
|
type ChannelConfigureBase = Pick<ChannelBindingBase, 'tenant' | 'apiKeySecret' | 'deploymentId' | 'capabilities' | 'supportEmail'> & {
|
|
73
80
|
readonly limits?: ChannelLimitsInput | undefined;
|
|
74
81
|
};
|
|
@@ -79,7 +86,8 @@ export type WhatsAppConfigure = ChannelConfigureBase & Pick<WhatsAppBinding, 'ph
|
|
|
79
86
|
readonly provider?: ChannelProvider | undefined;
|
|
80
87
|
};
|
|
81
88
|
export type InstagramConfigure = ChannelConfigureBase & Pick<InstagramBinding, 'channel' | 'instagramAccountId' | 'pageId'>;
|
|
82
|
-
export type
|
|
89
|
+
export type SmsConfigure = ChannelConfigureBase & Pick<SmsBinding, 'channel' | 'accountSid' | 'phoneNumberSid' | 'phoneNumber'>;
|
|
90
|
+
export type ChannelConfigure = WhatsAppConfigure | InstagramConfigure | SmsConfigure;
|
|
83
91
|
export interface ChannelAddress {
|
|
84
92
|
readonly kind: 'phone' | 'opaque';
|
|
85
93
|
readonly value: string;
|
|
@@ -130,6 +138,8 @@ export interface ChannelEvent {
|
|
|
130
138
|
readonly code?: string;
|
|
131
139
|
}
|
|
132
140
|
export interface ChannelInbound {
|
|
141
|
+
/** Authenticated provider opt-out control, never model input. */
|
|
142
|
+
readonly control?: 'stop' | 'start' | 'help';
|
|
133
143
|
readonly providerId: string;
|
|
134
144
|
readonly address: ChannelAddress;
|
|
135
145
|
readonly eventAt: number;
|
|
@@ -179,6 +189,27 @@ export declare function publicChannelBinding(binding: ChannelBinding): {
|
|
|
179
189
|
actor: string;
|
|
180
190
|
readyRevision?: number | undefined;
|
|
181
191
|
readyAt?: number | undefined;
|
|
192
|
+
} | {
|
|
193
|
+
channel: "sms";
|
|
194
|
+
provider: "twilio";
|
|
195
|
+
accountSid: string;
|
|
196
|
+
phoneNumberSid: string;
|
|
197
|
+
phoneNumber: string;
|
|
198
|
+
id: string;
|
|
199
|
+
tenant: TenantRef;
|
|
200
|
+
apiKeySecret: string;
|
|
201
|
+
deploymentId: string;
|
|
202
|
+
capabilities: readonly ChannelCapability[];
|
|
203
|
+
supportEmail: string;
|
|
204
|
+
revision: number;
|
|
205
|
+
generation: number;
|
|
206
|
+
state: "paused" | "enabled" | "disconnected";
|
|
207
|
+
limits: ChannelLimits;
|
|
208
|
+
createdAt: number;
|
|
209
|
+
updatedAt: number;
|
|
210
|
+
actor: string;
|
|
211
|
+
readyRevision?: number | undefined;
|
|
212
|
+
readyAt?: number | undefined;
|
|
182
213
|
} | {
|
|
183
214
|
provider: ChannelProvider;
|
|
184
215
|
phoneNumberId: string;
|
|
@@ -15,7 +15,7 @@ export const CHANNEL_DEFAULT_LIMITS = Object.freeze({
|
|
|
15
15
|
dailyMicroUsd: 20_000_000,
|
|
16
16
|
});
|
|
17
17
|
/** One binding per channel per environment; each channel owns its own provider asset. */
|
|
18
|
-
export const CHANNEL_KINDS = ['whatsapp', 'instagram'];
|
|
18
|
+
export const CHANNEL_KINDS = ['whatsapp', 'instagram', 'sms'];
|
|
19
19
|
export class ChannelError extends Error {
|
|
20
20
|
code;
|
|
21
21
|
constructor(code) {
|
|
@@ -39,7 +39,11 @@ export function channelAssetKey(channel, assetId) {
|
|
|
39
39
|
: `asset:${channel}:${channelDigest(assetId)}`;
|
|
40
40
|
}
|
|
41
41
|
export function channelBindingAsset(binding) {
|
|
42
|
-
return channelAssetKey(binding.channel, binding.channel === '
|
|
42
|
+
return channelAssetKey(binding.channel, binding.channel === 'sms'
|
|
43
|
+
? binding.phoneNumber
|
|
44
|
+
: binding.channel === 'whatsapp'
|
|
45
|
+
? binding.phoneNumberId
|
|
46
|
+
: binding.instagramAccountId);
|
|
43
47
|
}
|
|
44
48
|
export function channelParticipantId(binding, address) {
|
|
45
49
|
return `p_${createHmac('sha256', Buffer.from(binding.indexKey, 'base64'))
|
|
@@ -80,7 +84,7 @@ export async function writeChannelEvent(tx, scope, event, now) {
|
|
|
80
84
|
}
|
|
81
85
|
/** WhatsApp rows are stored without `channel`, byte-identical to rows written before Instagram. */
|
|
82
86
|
function storedChannelBinding(binding) {
|
|
83
|
-
if (binding.channel
|
|
87
|
+
if (binding.channel !== 'whatsapp')
|
|
84
88
|
return binding;
|
|
85
89
|
const { channel: _whatsapp, ...stored } = binding;
|
|
86
90
|
return stored;
|
|
@@ -91,7 +95,9 @@ export function channelBindingRow(binding, now) {
|
|
|
91
95
|
/** Rows written before Instagram carry no `channel`; they are WhatsApp. Nothing is written back. */
|
|
92
96
|
export function readChannelBinding(stored) {
|
|
93
97
|
const binding = stored;
|
|
94
|
-
return binding.channel === 'instagram'
|
|
98
|
+
return binding.channel === 'instagram' || binding.channel === 'sms'
|
|
99
|
+
? binding
|
|
100
|
+
: { ...binding, channel: 'whatsapp' };
|
|
95
101
|
}
|
|
96
102
|
export function publicChannelBinding(binding) {
|
|
97
103
|
const { indexKey: _privateIndexKey, credentialDigest: _credentialDigest, ...visible } = storedChannelBinding(binding);
|
|
@@ -224,7 +224,7 @@ export interface PublicWebsiteAccess {
|
|
|
224
224
|
}
|
|
225
225
|
export interface PublicMessagingInput {
|
|
226
226
|
/** One surface per channel; the operator binds each channel's account separately. */
|
|
227
|
-
readonly channel: 'whatsapp' | 'instagram';
|
|
227
|
+
readonly channel: 'whatsapp' | 'instagram' | 'sms';
|
|
228
228
|
readonly capabilities: readonly CapabilityRef[];
|
|
229
229
|
readonly instructions?: string;
|
|
230
230
|
/** @see AuthenticatedWebsiteInput.history */
|