@noodleseed/one 0.173.0 → 0.175.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-activity.js +1 -1
- package/dist/commands/solutions-activity.js.map +1 -1
- package/dist/commands/solutions-drafts.d.ts.map +1 -1
- package/dist/commands/solutions-drafts.js +36 -9
- package/dist/commands/solutions-drafts.js.map +1 -1
- package/dist/commands/solutions-native-lifecycle.d.ts +5 -0
- package/dist/commands/solutions-native-lifecycle.d.ts.map +1 -0
- package/dist/commands/solutions-native-lifecycle.js +77 -0
- package/dist/commands/solutions-native-lifecycle.js.map +1 -0
- package/dist/commands/solutions-ops-args.d.ts +42 -0
- package/dist/commands/solutions-ops-args.d.ts.map +1 -0
- package/dist/commands/solutions-ops-args.js +47 -0
- package/dist/commands/solutions-ops-args.js.map +1 -0
- package/dist/commands/solutions-ops.d.ts.map +1 -1
- package/dist/commands/solutions-ops.js +9 -46
- package/dist/commands/solutions-ops.js.map +1 -1
- package/dist/commands/solutions-workspace.d.ts +5 -0
- package/dist/commands/solutions-workspace.d.ts.map +1 -0
- package/dist/commands/solutions-workspace.js +157 -0
- package/dist/commands/solutions-workspace.js.map +1 -0
- package/dist/commands/whatsapp-output.d.ts.map +1 -1
- package/dist/commands/whatsapp-output.js +20 -4
- package/dist/commands/whatsapp-output.js.map +1 -1
- package/node_modules/@noodle-borg/admission-limits/dist/public-record-admission.d.ts +16 -9
- package/node_modules/@noodle-borg/admission-limits/dist/public-record-admission.js +5 -2
- package/node_modules/@noodle-borg/agent-kit/dist/curated/error-fixes.js +9 -0
- package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +5 -5
- package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js +9 -0
- package/node_modules/@noodle-borg/agent-kit/dist/skill-authoring-refs.js +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +13 -1
- package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-coordinator.d.ts +5 -3
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-coordinator.js +5 -4
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-inbox.js +6 -2
- 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 +12 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-work.d.ts +7 -2
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-work.js +14 -4
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-capture.js +104 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-judgment.js +93 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-ledger.d.ts +105 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-ledger.js +183 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-model-call.js +38 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-parsers.d.ts +22 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-parsers.js +109 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-review.d.ts +21 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-review.js +44 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-spec.d.ts +18 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-spec.js +86 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-store.d.ts +11 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-store.js +18 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/messaging-tool-dispatch.d.ts +20 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/messaging-tool-dispatch.js +38 -2
- package/node_modules/@noodle-borg/assistant-gateway/dist/model-runtime.js +3 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.d.ts +5 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.js +5 -0
- package/node_modules/@noodle-borg/authoring/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/authoring/dist/interaction.d.ts +49 -0
- package/node_modules/@noodle-borg/authoring/dist/interaction.js +25 -0
- package/node_modules/@noodle-borg/authoring/dist/server.d.ts +3 -0
- package/node_modules/@noodle-borg/authoring/dist/server.js +4 -0
- package/node_modules/@noodle-borg/authoring/package.json +6 -1
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-channels.js +1 -1
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-drafts.js +7 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-native-lifecycle.js +43 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-records.js +154 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-workspace.js +95 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solutions.js +4 -149
- package/node_modules/@noodle-borg/compiler/dist/artifact/assemble.js +2 -1
- package/node_modules/@noodle-borg/compiler/dist/artifact/types.d.ts +33 -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 +5 -0
- package/node_modules/@noodle-borg/compiler/dist/errors.d.ts +1 -1
- package/node_modules/@noodle-borg/compiler/dist/index.d.ts +2 -2
- package/node_modules/@noodle-borg/compiler/dist/index.js +1 -1
- package/node_modules/@noodle-borg/compiler/dist/manifest/interaction-schema.d.ts +41 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/interaction-schema.js +62 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/interaction-validation.js +220 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +60 -4
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +7 -1
- package/node_modules/@noodle-borg/compiler/dist/manifest/website-projection.d.ts +9 -1
- package/node_modules/@noodle-borg/compiler/dist/manifest/website-projection.js +58 -10
- package/node_modules/@noodle-borg/control-plane/dist/in-memory-atomic-state.d.ts +8 -0
- package/node_modules/@noodle-borg/control-plane/dist/in-memory-atomic-state.js +136 -0
- package/node_modules/@noodle-borg/control-plane/dist/in-memory-control-plane-store.d.ts +7 -0
- package/node_modules/@noodle-borg/control-plane/dist/in-memory-control-plane-store.js +16 -24
- package/node_modules/@noodle-borg/control-plane/dist/in-memory-organization-store.d.ts +4 -2
- package/node_modules/@noodle-borg/control-plane/dist/in-memory-organization-store.js +8 -6
- package/node_modules/@noodle-borg/control-plane/dist/mcp-subdomain-claims.d.ts +2 -1
- package/node_modules/@noodle-borg/control-plane/dist/mcp-subdomain-claims.js +13 -8
- package/node_modules/@noodle-borg/control-plane/dist/organization-agreements.d.ts +13 -4
- package/node_modules/@noodle-borg/control-plane/dist/organization-agreements.js +23 -7
- package/node_modules/@noodle-borg/control-plane/dist/portable.d.ts +2 -1
- package/node_modules/@noodle-borg/control-plane/dist/portable.js +1 -0
- package/node_modules/@noodle-borg/module/dist/platform-human-identity.d.ts +5 -2
- package/node_modules/@noodle-borg/runtime/dist/connector/types.d.ts +16 -4
- package/node_modules/@noodle-borg/runtime/dist/execute.d.ts +3 -6
- package/node_modules/@noodle-borg/runtime/dist/index.d.ts +1 -1
- package/node_modules/@noodle-borg/service/dist/application-activity.js +85 -63
- package/node_modules/@noodle-borg/service/dist/application-drafts/contracts.js +9 -0
- package/node_modules/@noodle-borg/service/dist/application-drafts/paths.js +2 -2
- package/node_modules/@noodle-borg/service/dist/application-runtime-target.js +1 -1
- package/node_modules/@noodle-borg/service/dist/business-information/contracts.d.ts +18 -4
- package/node_modules/@noodle-borg/service/dist/business-information/in-memory-business-pages.js +3 -5
- package/node_modules/@noodle-borg/service/dist/business-information/in-memory-installation-lifecycle.js +8 -6
- package/node_modules/@noodle-borg/service/dist/business-information/in-memory-native-lifecycle.js +80 -0
- package/node_modules/@noodle-borg/service/dist/business-information/in-memory-store.js +27 -31
- package/node_modules/@noodle-borg/service/dist/business-information/memory-native-retention.js +29 -0
- package/node_modules/@noodle-borg/service/dist/business-information/model.js +9 -2
- package/node_modules/@noodle-borg/service/dist/business-information/native-query.js +3 -1
- package/node_modules/@noodle-borg/service/dist/business-information/native-record-lifecycle.d.ts +27 -0
- package/node_modules/@noodle-borg/service/dist/business-information/native-record-lifecycle.js +63 -0
- package/node_modules/@noodle-borg/service/dist/business-information/principal-authority.d.ts +3 -1
- package/node_modules/@noodle-borg/service/dist/business-information/principal-authority.js +9 -4
- package/node_modules/@noodle-borg/service/dist/business-information/staff-authority.d.ts +16 -0
- package/node_modules/@noodle-borg/service/dist/business-information/staff-authority.js +60 -0
- package/node_modules/@noodle-borg/service/dist/business-information/workspace-permissions.js +18 -0
- package/node_modules/@noodle-borg/service/dist/business-information-runtime.js +1 -0
- package/node_modules/@noodle-borg/service/dist/business-onboarding.js +16 -3
- package/node_modules/@noodle-borg/service/dist/business-workspaces/contracts.d.ts +110 -0
- package/node_modules/@noodle-borg/service/dist/business-workspaces/contracts.js +52 -0
- package/node_modules/@noodle-borg/service/dist/business-workspaces/paths.js +17 -0
- package/node_modules/@noodle-borg/service/dist/business-workspaces/permissions.d.ts +7 -0
- package/node_modules/@noodle-borg/service/dist/business-workspaces/store.d.ts +102 -0
- package/node_modules/@noodle-borg/service/dist/channels/360dialog.js +70 -8
- package/node_modules/@noodle-borg/service/dist/channels/capability-report.js +73 -0
- package/node_modules/@noodle-borg/service/dist/channels/collection-turn.js +232 -0
- package/node_modules/@noodle-borg/service/dist/channels/composition.js +1 -0
- package/node_modules/@noodle-borg/service/dist/channels/messaging-eligibility.js +45 -0
- package/node_modules/@noodle-borg/service/dist/channels/reply-buttons.js +23 -0
- package/node_modules/@noodle-borg/service/dist/channels/runtime.js +127 -33
- package/node_modules/@noodle-borg/service/dist/native-record-connector.js +19 -4
- package/node_modules/@noodle-borg/service/dist/routes/application-drafts.js +44 -2
- package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +4 -0
- package/node_modules/@noodle-borg/service/dist/routes/business-authoring-dispatch.js +23 -0
- package/node_modules/@noodle-borg/service/dist/routes/business-information-access.js +39 -0
- package/node_modules/@noodle-borg/service/dist/routes/business-information-activity.js +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/business-information-channels.js +2 -2
- package/node_modules/@noodle-borg/service/dist/routes/business-information-connections.js +10 -4
- package/node_modules/@noodle-borg/service/dist/routes/business-information-dispatch.js +11 -6
- package/node_modules/@noodle-borg/service/dist/routes/business-information-installation.js +3 -1
- package/node_modules/@noodle-borg/service/dist/routes/business-information-notice.js +6 -2
- package/node_modules/@noodle-borg/service/dist/routes/business-information-pages.js +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/business-information-paths.js +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/business-information-record-create.js +7 -2
- package/node_modules/@noodle-borg/service/dist/routes/business-information-record-reads.js +2 -2
- package/node_modules/@noodle-borg/service/dist/routes/business-information-records.js +332 -0
- package/node_modules/@noodle-borg/service/dist/routes/business-information-settings.js +6 -3
- package/node_modules/@noodle-borg/service/dist/routes/business-information-staff.js +9 -5
- package/node_modules/@noodle-borg/service/dist/routes/business-information.js +12 -356
- package/node_modules/@noodle-borg/service/dist/routes/business-native-lifecycle.js +46 -0
- package/node_modules/@noodle-borg/service/dist/routes/business-workspaces.js +119 -0
- package/node_modules/@noodle-borg/service/dist/routes/service-probes.js +2 -1
- package/node_modules/@noodle-borg/service/dist/service.js +9 -2
- package/node_modules/@noodle-borg/service/dist/web-capabilities.js +3 -1
- package/node_modules/@noodle-borg/service/package.json +2 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/application-draft-validation.d.ts +59 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/application-draft-validation.js +38 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/business-information.d.ts +144 -50
- package/node_modules/@noodle-borg/wire-contracts/dist/business-information.js +44 -4
- package/node_modules/@noodle-borg/wire-contracts/dist/business-workspaces.d.ts +258 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/business-workspaces.js +118 -1
- 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/native-record-lifecycle.d.ts +81 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/native-record-lifecycle.js +39 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/whatsapp.d.ts +26 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/whatsapp.js +24 -1
- package/package.json +2 -2
|
@@ -176,6 +176,15 @@ export const COMPILE_ERROR_CODES = [
|
|
|
176
176
|
'assistant_capability_unknown',
|
|
177
177
|
'assistant_public_user_reference',
|
|
178
178
|
'assistant_public_effect_unconfirmed',
|
|
179
|
+
'interaction_action_invalid',
|
|
180
|
+
'interaction_field_unknown',
|
|
181
|
+
'interaction_field_control_mismatch',
|
|
182
|
+
'interaction_field_missing',
|
|
183
|
+
'interaction_initial_value_unknown',
|
|
184
|
+
'interaction_consent_invalid',
|
|
185
|
+
'interaction_expiry_invalid',
|
|
186
|
+
'channel_dependency_missing',
|
|
187
|
+
'channel_requirement_unsupported',
|
|
179
188
|
'customer_endpoint_action_unsupported',
|
|
180
189
|
'customer_endpoint_surface_unsupported',
|
|
181
190
|
'customer_endpoint_credential_source_unsupported',
|
|
@@ -274,7 +274,7 @@ export function renderAuthoringWorkflowReference() {
|
|
|
274
274
|
'Omitting `source` makes Noodle authoritative for the collection. To project a read-only collection from an outside system, bind an HTTP connector in `server(..., { use: [...] })` and add `source: { connector: connector.ref(), scan: "scan_operation" }`. The compiler requires the exact normalized scan contract and the same record schema. Noodle ingests a one-way replica; create, update, and delete in the outside system remain ordinary application tools with their own confirmation and policy.',
|
|
275
275
|
'',
|
|
276
276
|
'For an explicitly authored native operation, bind `use: { records: noodlePlatform.records.v1 }` and call `connectors.records.submitRecord({ collection: "guest_requests", payload: input })` from an ordinary confirmed tool. Submission accepts only public fields and returns a receipt. `createRecord`, `getRecord`, `listRecords`, `updateRecord`, and `deleteRecord` require a verified platform caller with a live business grant; updates/deletes require the current revision. `updateRecord({ collection, id, expectedRevision, patch, unset: ["optional_field"] })` removes named optional editable fields; omitted keys stay unchanged, `null` is data, and overlap with `patch`, required fields or undeclared fields is rejected. Runtime identity supplies write idempotency and admission; never accept those controls as model arguments. An uninstalled collection is unavailable, including during local tool execution.',
|
|
277
|
-
'A collection declaration does not create a submit tool, grant access, select retention or residency, provision storage, or expose records to MCP or an embedded assistant. Operators activate the definition and configure lifecycle, access, and any external binding. A connector does not become a collection unless the app explicitly supplies the source contract, and an external collection never becomes a writable mirror.',
|
|
277
|
+
'A collection declaration does not create a submit tool, grant access, select retention or residency, provision storage, or expose records to MCP or an embedded assistant. Operators activate the definition and configure lifecycle, access, and any external binding. Native records retain legacy creation-time expiry until an Owner/Admin explicitly reviews and confirms preservation through Business settings or `noodle solutions records lifecycle`. Then expiry is null (until explicit erasure); activity history caps remain separate. Never change expiry in authored tools or infer an appointment was fulfilled from a saved request. A connector does not become a collection unless the app explicitly supplies the source contract, and an external collection never becomes a writable mirror.',
|
|
278
278
|
'',
|
|
279
279
|
'## Boundaries',
|
|
280
280
|
'',
|
|
@@ -7,6 +7,7 @@ export function renderEmbeddedAssistantReference() {
|
|
|
7
7
|
'',
|
|
8
8
|
'## Contents',
|
|
9
9
|
'',
|
|
10
|
+
'- Collect interactions',
|
|
10
11
|
'- Architecture',
|
|
11
12
|
'- Select the architecture before code',
|
|
12
13
|
'- First-workflow default: continuous onboarding',
|
|
@@ -30,10 +31,21 @@ export function renderEmbeddedAssistantReference() {
|
|
|
30
31
|
'',
|
|
31
32
|
'## WhatsApp public alpha',
|
|
32
33
|
'',
|
|
33
|
-
'Reuse the same server and public read-only capabilities with `publicMessaging({ channel: "whatsapp", capabilities: [product, siteIdentity] })` in `embeddedAssistant.access`. Import `publicMessaging` from `@noodleseed/one`. This projection is explicit:
|
|
34
|
+
'Reuse the same server and public read-only capabilities with `publicMessaging({ channel: "whatsapp", capabilities: [product, siteIdentity] })` in `embeddedAssistant.access`. Import `publicMessaging` from `@noodleseed/one`. This projection is explicit: knowledge, pure public reads, and a `collect` opener listed with its confirmed native-record action. The platform’s natural collection gathers the opener’s fields in conversation, asks the consent question, renders the review verbatim and confirms; a field marked `private: true` never reaches the conversation model or the transcript. Other writes, connector-backed lookups, customer identity and browser widgets without an interaction are not supported. A WhatsApp participant is anonymous; never infer a customer account from its phone number.',
|
|
34
35
|
'',
|
|
35
36
|
'Operator setup uses `noodle channels whatsapp configure`, managed secret references, `webhook configure`, `doctor`, and `enable`. Start paused. Only a service advertising `features.whatsapp: 1`, durable storage, a running worker, a verified cost bound and matching provider webhook can enable. Use `noodle channels whatsapp --help` for the complete operator command catalog. Keep real keys in `noodle secrets`, never in source, flags or messages. ChatGPT and Claude continue to use the same app MCP endpoint with their own host configuration.',
|
|
36
37
|
'',
|
|
38
|
+
'## Collect interactions',
|
|
39
|
+
'',
|
|
40
|
+
'An opener `tool()` may declare `interaction: { kind: "collect", action, fields, review: "all", outcome }`: one definition of a form-like exchange that Noodle Seed presents with the controls each channel has. On the website the tool’s React `view` (or a standard form) collects the fields; on a messaging channel the platform renderer gathers the same fields in natural conversation, renders the review verbatim, and saves through the same confirmed `action`. The block carries meaning, never layout, ordering or screen language; `noodle validate` checks it against the action’s input schema.',
|
|
41
|
+
'',
|
|
42
|
+
'- `action` is a tool with `annotations.openAction({ confirm: true })` (never read-only) whose fulfilment saves one native record through `connectors.records`.',
|
|
43
|
+
'- `fields` name the action’s Zod input keys with one of seven controls: `text`, `textarea`, `email`, `phone`, `url`, `select` (a `z.enum` of strings) and `consent` (`z.literal(true)`, at most one; the runtime poses the consent question and never infers it).',
|
|
44
|
+
'- Nothing is private by default. `private: true` keeps that value out of the conversation model, transcript, logs and diagnostics. `optional: true` lets a person skip a field; an optional field the schema still requires must accept `""`.',
|
|
45
|
+
'- `initialValues: { workflow: { fromOutput: "workflow" } }` seeds action inputs from the opener’s `output`; every other required action input must be a field.',
|
|
46
|
+
'- `confirmationExpiry: { seconds }` may only shorten the channel’s default (ten minutes on WhatsApp), never lengthen it.',
|
|
47
|
+
'- On `publicMessaging`, list the opener and its action together. The compiler reports `channel_dependency_missing` for an opener selected alone and `channel_requirement_unsupported` for a widget, write or external connector call that has no collect interaction.',
|
|
48
|
+
'',
|
|
37
49
|
'## Architecture',
|
|
38
50
|
'',
|
|
39
51
|
'The browser never receives a model key, assistant client secret, MCP token, or raw application session. The embedding SaaS authenticates its own user, its backend exchanges that verified identity through `@noodleseed/assistant/server`, and the browser receives only a short-lived assistant session.',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ChannelStore, ChannelTransaction } from './channel-store.js';
|
|
2
|
-
import { type ChannelBinding, type ChannelConfigure, type ChannelEvent, type ChannelInbound, type ChannelLimitsInput } from './channel-types.js';
|
|
2
|
+
import { type ChannelBinding, type ChannelConfigure, type ChannelEvent, type ChannelInbound, type ChannelLimitsInput, type ChannelReplyButton } from './channel-types.js';
|
|
3
|
+
import { type ChannelTranscript } from './channel-work.js';
|
|
3
4
|
import type { TenantRef } from './tenant-ref.js';
|
|
4
5
|
export interface ChannelBlock {
|
|
5
6
|
readonly participantId: string;
|
|
@@ -115,11 +116,12 @@ export declare class ChannelCoordinator {
|
|
|
115
116
|
expireHistory(id: string, retentionMs: number): Promise<void>;
|
|
116
117
|
claimTool(id: string, participantId: string, name: string): Promise<boolean>;
|
|
117
118
|
event(id: string, eventId: string): Promise<ChannelEvent | undefined>;
|
|
118
|
-
|
|
119
|
+
/** Close a running turn without a reply: `failed` for an error, `cancelled` for input that is deliberately not answered. */
|
|
120
|
+
fail(id: string, eventId: string, lease: string, code: string, state?: 'failed' | 'cancelled'): Promise<void>;
|
|
119
121
|
receive(id: string, messages: readonly ChannelInbound[], retentionMs?: number): Promise<Pick<ChannelEvent, "state" | "id" | "code" | "participantId">[]>;
|
|
120
122
|
claim(id: string, retentionMs?: number): Promise<import("./channel-work.js").ChannelWork | undefined>;
|
|
121
123
|
renew(id: string, eventId: string, lease: string): Promise<void>;
|
|
122
|
-
complete(id: string, eventId: string, lease: string, reply: string, retentionMs?: number, code?: string): Promise<void>;
|
|
124
|
+
complete(id: string, eventId: string, lease: string, reply: string, retentionMs?: number, code?: string, transcript?: ChannelTranscript, buttons?: readonly ChannelReplyButton[]): Promise<void>;
|
|
123
125
|
assertSend(id: string, eventId: string, lease: string): Promise<void>;
|
|
124
126
|
prepareSend(id: string): Promise<import("./channel-work.js").ChannelWork | undefined>;
|
|
125
127
|
sent(id: string, eventId: string, lease: string, result: {
|
|
@@ -336,12 +336,13 @@ export class ChannelCoordinator {
|
|
|
336
336
|
return channelValue(tx, id, eventId);
|
|
337
337
|
});
|
|
338
338
|
}
|
|
339
|
-
|
|
339
|
+
/** Close a running turn without a reply: `failed` for an error, `cancelled` for input that is deliberately not answered. */
|
|
340
|
+
async fail(id, eventId, lease, code, state = 'failed') {
|
|
340
341
|
await this.store.transaction([id], async (tx) => {
|
|
341
342
|
const event = await channelValue(tx, id, eventId);
|
|
342
343
|
if (!event || event.lease !== lease || event.state !== 'running')
|
|
343
344
|
return;
|
|
344
|
-
await writeChannelEvent(tx, id, { ...event, state
|
|
345
|
+
await writeChannelEvent(tx, id, { ...event, state, code }, this.now());
|
|
345
346
|
});
|
|
346
347
|
}
|
|
347
348
|
receive(id, messages, retentionMs = CHANNEL_RETENTION_MS) {
|
|
@@ -353,8 +354,8 @@ export class ChannelCoordinator {
|
|
|
353
354
|
renew(id, eventId, lease) {
|
|
354
355
|
return renewChannelTurn(this.store, id, eventId, lease, this.now());
|
|
355
356
|
}
|
|
356
|
-
complete(id, eventId, lease, reply, retentionMs = CHANNEL_RETENTION_MS, code) {
|
|
357
|
-
return completeChannelTurn(this.store, id, eventId, lease, reply, this.now(), retentionMs, code);
|
|
357
|
+
complete(id, eventId, lease, reply, retentionMs = CHANNEL_RETENTION_MS, code, transcript, buttons) {
|
|
358
|
+
return completeChannelTurn(this.store, id, eventId, lease, reply, this.now(), retentionMs, code, transcript, buttons);
|
|
358
359
|
}
|
|
359
360
|
assertSend(id, eventId, lease) {
|
|
360
361
|
return assertChannelSend(this.store, id, eventId, lease, this.now());
|
|
@@ -67,7 +67,10 @@ export async function receiveChannelMessages(store, id, messages, now, retention
|
|
|
67
67
|
const sequence = ((await channelValue(tx, id, 'sequence')) ?? 0) + 1;
|
|
68
68
|
await tx.put(id, channelRow('sequence', 'counter', sequence, now));
|
|
69
69
|
const eventId = `e_${String(sequence).padStart(20, '0')}`;
|
|
70
|
-
const
|
|
70
|
+
const text = message.text !== undefined && message.text.length <= binding.limits.textCharacters
|
|
71
|
+
? message.text
|
|
72
|
+
: undefined;
|
|
73
|
+
const unsupported = text === undefined && message.button === undefined;
|
|
71
74
|
let event = {
|
|
72
75
|
id: eventId,
|
|
73
76
|
participantId,
|
|
@@ -79,7 +82,8 @@ export async function receiveChannelMessages(store, id, messages, now, retention
|
|
|
79
82
|
attempts: 0,
|
|
80
83
|
state: code ? 'refused' : unsupported ? 'reply' : 'queued',
|
|
81
84
|
...(code ? { code } : {}),
|
|
82
|
-
...(!code &&
|
|
85
|
+
...(!code && text !== undefined ? { text } : {}),
|
|
86
|
+
...(!code && message.button !== undefined ? { button: message.button } : {}),
|
|
83
87
|
...(!code && unsupported
|
|
84
88
|
? {
|
|
85
89
|
reply: `Please send a text message of up to ${binding.limits.textCharacters} characters. For help, contact ${binding.supportEmail}.`,
|
|
@@ -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';
|
|
4
|
+
readonly kind: 'binding' | 'asset' | 'participant' | 'event' | 'counter' | 'spend' | 'block' | 'mutation' | 'interaction';
|
|
5
5
|
readonly state?: string;
|
|
6
6
|
readonly updatedAt: number;
|
|
7
7
|
readonly expiresAt?: number;
|
|
@@ -71,13 +71,24 @@ export interface ChannelParticipant {
|
|
|
71
71
|
readonly cooldownUntil?: number;
|
|
72
72
|
}
|
|
73
73
|
export type ChannelEventState = 'queued' | 'running' | 'reply' | 'sending' | 'accepted' | 'delivered' | 'read' | 'refused' | 'expired' | 'failed' | 'cancelled' | 'unknown';
|
|
74
|
+
/** A native reply button: an opaque server-bound id and the label the participant sees. */
|
|
75
|
+
export interface ChannelReplyButton {
|
|
76
|
+
readonly id: string;
|
|
77
|
+
readonly title: string;
|
|
78
|
+
}
|
|
74
79
|
export interface ChannelEvent {
|
|
75
80
|
readonly id: string;
|
|
76
81
|
readonly participantId: string;
|
|
77
82
|
readonly providerId: string;
|
|
78
83
|
readonly providerMessageId?: string;
|
|
79
84
|
readonly text?: string;
|
|
85
|
+
/** The button the participant tapped, in place of text. */
|
|
86
|
+
readonly button?: ChannelReplyButton;
|
|
80
87
|
readonly reply?: string;
|
|
88
|
+
/** What history keeps of the reply when it must differ from the sent text; replaces `reply` once dispatched. */
|
|
89
|
+
readonly replyTranscript?: string;
|
|
90
|
+
/** Native reply buttons offered with the reply; content-free, dropped once dispatch is attempted. */
|
|
91
|
+
readonly buttons?: readonly ChannelReplyButton[];
|
|
81
92
|
readonly eventAt: number;
|
|
82
93
|
readonly receivedAt: number;
|
|
83
94
|
readonly generation: number;
|
|
@@ -94,6 +105,7 @@ export interface ChannelInbound {
|
|
|
94
105
|
readonly address: ChannelAddress;
|
|
95
106
|
readonly eventAt: number;
|
|
96
107
|
readonly text?: string;
|
|
108
|
+
readonly button?: ChannelReplyButton;
|
|
97
109
|
}
|
|
98
110
|
export declare class ChannelError extends Error {
|
|
99
111
|
readonly code: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ChannelStore } from './channel-store.js';
|
|
2
|
-
import { type ChannelBinding, type ChannelEvent, type ChannelParticipant } from './channel-types.js';
|
|
2
|
+
import { type ChannelBinding, type ChannelEvent, type ChannelParticipant, type ChannelReplyButton } from './channel-types.js';
|
|
3
3
|
export interface ChannelWork {
|
|
4
4
|
readonly binding: ChannelBinding;
|
|
5
5
|
readonly participant: ChannelParticipant;
|
|
@@ -7,7 +7,12 @@ export interface ChannelWork {
|
|
|
7
7
|
}
|
|
8
8
|
export declare function claimChannelTurn(store: ChannelStore, id: string, now: number, retentionMs?: number): Promise<ChannelWork | undefined>;
|
|
9
9
|
export declare function renewChannelTurn(store: ChannelStore, id: string, eventId: string, lease: string, now: number): Promise<void>;
|
|
10
|
-
|
|
10
|
+
/** Text the transcript keeps instead of the raw message or the sent reply, e.g. with private values redacted. */
|
|
11
|
+
export interface ChannelTranscript {
|
|
12
|
+
readonly user?: string;
|
|
13
|
+
readonly assistant?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function completeChannelTurn(store: ChannelStore, id: string, eventId: string, lease: string, reply: string, now: number, retentionMs?: number, code?: string, transcript?: ChannelTranscript, buttons?: readonly ChannelReplyButton[]): Promise<void>;
|
|
11
16
|
export declare function prepareChannelSend(store: ChannelStore, id: string, now: number): Promise<ChannelWork | undefined>;
|
|
12
17
|
export declare function updateChannelSend(store: ChannelStore, id: string, eventId: string, lease: string, result: {
|
|
13
18
|
state: 'accepted' | 'unknown' | 'failed';
|
|
@@ -99,7 +99,7 @@ export async function renewChannelTurn(store, id, eventId, lease, now) {
|
|
|
99
99
|
await writeChannelEvent(tx, id, { ...event, leaseUntil: Math.min(now + LEASE_MS, event.turnDeadline) }, now);
|
|
100
100
|
});
|
|
101
101
|
}
|
|
102
|
-
export async function completeChannelTurn(store, id, eventId, lease, reply, now, retentionMs = CHANNEL_RETENTION_MS, code) {
|
|
102
|
+
export async function completeChannelTurn(store, id, eventId, lease, reply, now, retentionMs = CHANNEL_RETENTION_MS, code, transcript = {}, buttons) {
|
|
103
103
|
if (!reply.trim() || reply.length > 4096)
|
|
104
104
|
throw new ChannelError('reply_invalid');
|
|
105
105
|
await store.transaction([id], async (tx) => {
|
|
@@ -107,17 +107,23 @@ export async function completeChannelTurn(store, id, eventId, lease, reply, now,
|
|
|
107
107
|
const participant = await channelValue(tx, id, event.participantId);
|
|
108
108
|
if (!participant)
|
|
109
109
|
throw new ChannelError('context_expired');
|
|
110
|
+
const userText = transcript.user ?? event.text ?? '';
|
|
110
111
|
const history = [
|
|
111
112
|
...participant.history.filter((entry) => entry.at > now - retentionMs),
|
|
112
|
-
{ role: 'user', content:
|
|
113
|
-
{ role: 'assistant', content: reply, at: now },
|
|
113
|
+
{ role: 'user', content: userText, at: event.receivedAt },
|
|
114
|
+
{ role: 'assistant', content: transcript.assistant ?? reply, at: now },
|
|
114
115
|
].slice(-20);
|
|
115
116
|
await tx.put(id, channelRow(participant.id, 'participant', { ...participant, history }, now, {
|
|
116
117
|
expiresAt: participant.lastInboundAt + retentionMs,
|
|
117
118
|
}));
|
|
118
119
|
await writeChannelEvent(tx, id, {
|
|
119
120
|
...event,
|
|
121
|
+
...(event.text === undefined ? {} : { text: userText }),
|
|
120
122
|
reply,
|
|
123
|
+
...(transcript.assistant !== undefined && transcript.assistant !== reply
|
|
124
|
+
? { replyTranscript: transcript.assistant }
|
|
125
|
+
: {}),
|
|
126
|
+
...(buttons !== undefined && buttons.length > 0 ? { buttons } : {}),
|
|
121
127
|
state: 'reply',
|
|
122
128
|
lease: undefined,
|
|
123
129
|
leaseUntil: undefined,
|
|
@@ -160,8 +166,12 @@ export async function updateChannelSend(store, id, eventId, lease, result, now)
|
|
|
160
166
|
const early = result.providerMessageId
|
|
161
167
|
? await channelValue(tx, id, `delivery:${channelDigest(result.providerMessageId)}`)
|
|
162
168
|
: undefined;
|
|
169
|
+
// Once dispatch has been attempted the sent text and its buttons are never needed again: the
|
|
170
|
+
// row keeps only what the transcript keeps, so a redacted reply does not outlive its delivery.
|
|
171
|
+
const { replyTranscript: _kept, buttons: _offered, ...dispatched } = event;
|
|
163
172
|
await writeChannelEvent(tx, id, {
|
|
164
|
-
...
|
|
173
|
+
...dispatched,
|
|
174
|
+
...(event.replyTranscript === undefined ? {} : { reply: event.replyTranscript }),
|
|
165
175
|
...result,
|
|
166
176
|
...(early && early !== 'sent' ? { state: early } : {}),
|
|
167
177
|
leaseUntil: undefined,
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { validateJsonSchema } from '@noodle-borg/compiler';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { applyCapture, neededFields, } from './collection-ledger.js';
|
|
4
|
+
import { boundedJsonCall } from './collection-model-call.js';
|
|
5
|
+
import { normaliseTypedValue, redactSpans, scanTypedValues, } from './collection-parsers.js';
|
|
6
|
+
const PARSED = new Set(['email', 'phone', 'url']);
|
|
7
|
+
const extraction = z.record(z.string(), z.unknown());
|
|
8
|
+
function isParsed(control) {
|
|
9
|
+
return PARSED.has(control);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Deterministic step: a parser assigns a value only when exactly one candidate meets exactly one
|
|
13
|
+
* needed field of that control; competing candidates mark the field ambiguous. Every candidate,
|
|
14
|
+
* assigned or not, is redacted from the text before a model sees it.
|
|
15
|
+
*/
|
|
16
|
+
function parseTurn(needed, utterance) {
|
|
17
|
+
const controls = [...new Set(needed.map((field) => field.control).filter(isParsed))];
|
|
18
|
+
const spans = scanTypedValues(utterance, controls);
|
|
19
|
+
const outcome = {};
|
|
20
|
+
const assigned = new Set();
|
|
21
|
+
for (const control of controls) {
|
|
22
|
+
const fields = needed.filter((field) => field.control === control);
|
|
23
|
+
const candidates = spans.filter((span) => span.control === control);
|
|
24
|
+
if (candidates.length === 0)
|
|
25
|
+
continue;
|
|
26
|
+
const [onlyField] = fields;
|
|
27
|
+
const [onlyCandidate] = candidates;
|
|
28
|
+
if (fields.length === 1 && candidates.length === 1 && onlyField && onlyCandidate) {
|
|
29
|
+
outcome[onlyField.key] = { status: 'captured', value: onlyCandidate.value };
|
|
30
|
+
assigned.add(onlyCandidate);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const reason = candidates.length > 1 ? 'multiple_candidates' : 'multiple_fields';
|
|
34
|
+
for (const field of fields)
|
|
35
|
+
outcome[field.key] = { status: 'ambiguous', reason };
|
|
36
|
+
}
|
|
37
|
+
const redacted = redactSpans(utterance, spans, (span) => assigned.has(span) ? `[${span.control} captured]` : `[${span.control} withheld]`);
|
|
38
|
+
return { outcome, redacted };
|
|
39
|
+
}
|
|
40
|
+
/** Control rules first, then the action's own property schema; a failure is a code, never a value. */
|
|
41
|
+
function validateCandidate(field, raw) {
|
|
42
|
+
let value = raw;
|
|
43
|
+
if (isParsed(field.control)) {
|
|
44
|
+
value = typeof raw === 'string' ? normaliseTypedValue(field.control, raw) : undefined;
|
|
45
|
+
if (value === undefined)
|
|
46
|
+
return { status: 'invalid', reason: 'format' };
|
|
47
|
+
}
|
|
48
|
+
else if (field.control === 'select') {
|
|
49
|
+
const option = typeof raw === 'string'
|
|
50
|
+
? field.options?.find((candidate) => candidate.toLowerCase() === raw.trim().toLowerCase())
|
|
51
|
+
: undefined;
|
|
52
|
+
if (option === undefined)
|
|
53
|
+
return { status: 'invalid', reason: 'option' };
|
|
54
|
+
value = option;
|
|
55
|
+
}
|
|
56
|
+
else if (typeof raw === 'string') {
|
|
57
|
+
value = raw.trim();
|
|
58
|
+
}
|
|
59
|
+
if (validateJsonSchema(field.schema, value).length > 0)
|
|
60
|
+
return { status: 'invalid', reason: 'schema' };
|
|
61
|
+
return { status: 'captured', value };
|
|
62
|
+
}
|
|
63
|
+
function extractionInstruction(needed, ledger) {
|
|
64
|
+
const fields = needed.map((field) => ({
|
|
65
|
+
key: field.key,
|
|
66
|
+
title: field.title,
|
|
67
|
+
control: field.control,
|
|
68
|
+
...(field.options === undefined ? {} : { options: field.options }),
|
|
69
|
+
...(ledger.fields[field.key]?.status === 'rejected_by_user' && field.key in ledger.values
|
|
70
|
+
? {
|
|
71
|
+
current: ledger.values[field.key],
|
|
72
|
+
note: 'the user said this value is wrong; apply their correction to it',
|
|
73
|
+
}
|
|
74
|
+
: {}),
|
|
75
|
+
}));
|
|
76
|
+
return [
|
|
77
|
+
'Read the values the user states for these fields and reply with exactly one JSON object whose keys are the field keys.',
|
|
78
|
+
'Use null for any field the message does not state. Never guess, infer or invent a value.',
|
|
79
|
+
'Keep the user wording for text fields. For a select field return one of its options exactly, or null.',
|
|
80
|
+
'Bracketed placeholders such as [email captured] stand for values already taken; leave those fields null.',
|
|
81
|
+
`Fields: ${JSON.stringify(fields)}`,
|
|
82
|
+
].join('\n');
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* One user turn while a collection is open: parsers first, then at most one bounded extraction call
|
|
86
|
+
* for whatever still needs a value. Values reach the caller only inside the returned ledger.
|
|
87
|
+
*/
|
|
88
|
+
export async function capture(spec, ledger, utterance, deps, now) {
|
|
89
|
+
const needed = neededFields(spec, ledger);
|
|
90
|
+
const { outcome, redacted } = parseTurn(needed, utterance);
|
|
91
|
+
const remaining = needed.filter((field) => outcome[field.key] === undefined);
|
|
92
|
+
const content = redacted.replace(/\[(?:email|phone|url) (?:captured|withheld)\]/g, '').trim();
|
|
93
|
+
if (remaining.length > 0 && /[\p{L}\p{N}]/u.test(content)) {
|
|
94
|
+
const extracted = await boundedJsonCall(deps, extractionInstruction(remaining, ledger), redacted, extraction);
|
|
95
|
+
for (const field of remaining) {
|
|
96
|
+
const raw = extracted?.[field.key];
|
|
97
|
+
if (raw === undefined || raw === null)
|
|
98
|
+
continue;
|
|
99
|
+
outcome[field.key] = validateCandidate(field, raw);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return applyCapture(spec, ledger, outcome, now);
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=collection-capture.js.map
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { boundedJsonCall } from './collection-model-call.js';
|
|
3
|
+
import { scanTypedValues } from './collection-parsers.js';
|
|
4
|
+
/** A judged consent counts only at or above this confidence; below it the runtime re-asks. */
|
|
5
|
+
export const CONSENT_THRESHOLD = 0.85;
|
|
6
|
+
/** A judged confirmation binds a real write, so it is stricter than consent. */
|
|
7
|
+
export const CONFIRM_THRESHOLD = 0.95;
|
|
8
|
+
export const CONFIRMATION_AFFIRMATIVES = [
|
|
9
|
+
'yes',
|
|
10
|
+
'y',
|
|
11
|
+
'ok',
|
|
12
|
+
'okay',
|
|
13
|
+
'confirm',
|
|
14
|
+
'confirmed',
|
|
15
|
+
'send',
|
|
16
|
+
'send it',
|
|
17
|
+
'go ahead',
|
|
18
|
+
'sure',
|
|
19
|
+
'yes please',
|
|
20
|
+
'do it',
|
|
21
|
+
'looks good',
|
|
22
|
+
];
|
|
23
|
+
export const CONFIRMATION_NEGATIVES = [
|
|
24
|
+
'no',
|
|
25
|
+
'cancel',
|
|
26
|
+
'stop',
|
|
27
|
+
'never mind',
|
|
28
|
+
'nevermind',
|
|
29
|
+
"don't",
|
|
30
|
+
'dont',
|
|
31
|
+
'forget it',
|
|
32
|
+
];
|
|
33
|
+
/** Words that turn a reply naming a field into an edit; shared with the runtime's reopen rule. */
|
|
34
|
+
export const CORRECTION_CUES = /\b(change|changed|actually|instead|wrong|not|should be|make it|update|correct|rather|swap|replace|edit|fix)\b/;
|
|
35
|
+
const judgment = z.object({ affirmative: z.boolean(), confidence: z.number().min(0).max(1) });
|
|
36
|
+
const PARSED = ['email', 'phone', 'url'];
|
|
37
|
+
/** Lowercase, collapse whitespace, straighten quotes and drop trailing punctuation. */
|
|
38
|
+
export function normaliseReply(text) {
|
|
39
|
+
return text
|
|
40
|
+
.replace(/[‘’]/g, "'")
|
|
41
|
+
.toLowerCase()
|
|
42
|
+
.replace(/\s+/g, ' ')
|
|
43
|
+
.trim()
|
|
44
|
+
.replace(/[\s.!?,;:…]+$/u, '');
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Judge whether a free reply answers the fixed question affirmatively. The two messages carry the
|
|
48
|
+
* question and the reply only, never a collected value or the review.
|
|
49
|
+
*/
|
|
50
|
+
async function judgeAffirmative(deps, question, reply) {
|
|
51
|
+
return boundedJsonCall(deps, [
|
|
52
|
+
`The assistant asked the user this yes-or-no question: ${JSON.stringify(question)}`,
|
|
53
|
+
'Judge whether the reply is an affirmative answer to exactly that question.',
|
|
54
|
+
'A reply that asks for a change, adds a condition, gives new information or answers something else is not affirmative.',
|
|
55
|
+
'Reply with exactly one JSON object shaped {"affirmative": true or false, "confidence": a number from 0 to 1}.',
|
|
56
|
+
].join('\n'), reply, judgment);
|
|
57
|
+
}
|
|
58
|
+
export async function consentReply(text, spec, deps) {
|
|
59
|
+
const reply = normaliseReply(text);
|
|
60
|
+
if (reply === 'yes')
|
|
61
|
+
return 'given';
|
|
62
|
+
if (reply === 'no')
|
|
63
|
+
return 'declined';
|
|
64
|
+
const judged = await judgeAffirmative(deps, spec.consentQuestion, text);
|
|
65
|
+
if (judged === undefined || judged.confidence < CONSENT_THRESHOLD)
|
|
66
|
+
return 'unclear';
|
|
67
|
+
return judged.affirmative ? 'given' : 'declined';
|
|
68
|
+
}
|
|
69
|
+
/** A reply that offers a value or names a field with a correction cue is an edit, never a confirmation. */
|
|
70
|
+
function carriesEdit(text, spec) {
|
|
71
|
+
const controls = PARSED.filter((control) => spec.fields.some((field) => field.control === control));
|
|
72
|
+
if (scanTypedValues(text, controls).length > 0)
|
|
73
|
+
return true;
|
|
74
|
+
const lower = text.toLowerCase();
|
|
75
|
+
const namesField = spec.fields.some((field) => field.control !== 'consent' &&
|
|
76
|
+
(lower.includes(field.title.toLowerCase()) ||
|
|
77
|
+
new RegExp(`\\b${field.key}\\b`, 'i').test(text)));
|
|
78
|
+
return namesField && CORRECTION_CUES.test(lower);
|
|
79
|
+
}
|
|
80
|
+
export async function confirmationReply(text, spec, deps) {
|
|
81
|
+
if (carriesEdit(text, spec))
|
|
82
|
+
return 'edit';
|
|
83
|
+
const reply = normaliseReply(text);
|
|
84
|
+
if (CONFIRMATION_AFFIRMATIVES.includes(reply))
|
|
85
|
+
return 'confirm';
|
|
86
|
+
if (CONFIRMATION_NEGATIVES.includes(reply))
|
|
87
|
+
return 'cancel';
|
|
88
|
+
const judged = await judgeAffirmative(deps, 'Shall I send it? Yes or no.', text);
|
|
89
|
+
if (judged === undefined || !judged.affirmative || judged.confidence < CONFIRM_THRESHOLD)
|
|
90
|
+
return 'unclear';
|
|
91
|
+
return 'confirm';
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=collection-judgment.js.map
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { JsonSchema } from '@noodle-borg/compiler';
|
|
2
|
+
/**
|
|
3
|
+
* Runtime-owned natural collection ledger (ADR 0240 decision 5). The ledger is the pre-proposal
|
|
4
|
+
* stage of the shared confirmation lifecycle: once fields are complete the proposal it stores is an
|
|
5
|
+
* opaque prepared-tool continuation, never a second confirmation engine.
|
|
6
|
+
*/
|
|
7
|
+
export type CollectionControl = 'text' | 'textarea' | 'email' | 'phone' | 'url' | 'select' | 'consent';
|
|
8
|
+
export interface CollectionField {
|
|
9
|
+
readonly key: string;
|
|
10
|
+
readonly title: string;
|
|
11
|
+
readonly control: CollectionControl;
|
|
12
|
+
/** Never enters the conversation model, transcript, logs or diagnostics. */
|
|
13
|
+
readonly private?: true;
|
|
14
|
+
readonly optional?: true;
|
|
15
|
+
readonly options?: readonly string[];
|
|
16
|
+
/** The action input property this field fills; every candidate is validated against it. */
|
|
17
|
+
readonly schema: JsonSchema;
|
|
18
|
+
}
|
|
19
|
+
export interface CollectionSpec {
|
|
20
|
+
/** Opener tool name. */
|
|
21
|
+
readonly interactionId: string;
|
|
22
|
+
readonly action: string;
|
|
23
|
+
readonly fields: readonly CollectionField[];
|
|
24
|
+
readonly consentQuestion: string;
|
|
25
|
+
readonly successMessage: string;
|
|
26
|
+
readonly confirmationExpiryMs: number;
|
|
27
|
+
}
|
|
28
|
+
export type CollectionPhase = 'collecting' | 'awaiting_consent' | 'awaiting_confirmation' | 'executing' | 'completed' | 'cancelled' | 'expired';
|
|
29
|
+
export type CollectionFieldStatus = 'missing' | 'captured' | 'invalid' | 'ambiguous' | 'rejected_by_user';
|
|
30
|
+
export interface CollectionFieldState {
|
|
31
|
+
readonly status: CollectionFieldStatus;
|
|
32
|
+
/** Short structured code, never a value. */
|
|
33
|
+
readonly reason?: string;
|
|
34
|
+
readonly attempts: number;
|
|
35
|
+
}
|
|
36
|
+
export interface CollectionConsent {
|
|
37
|
+
readonly given: boolean;
|
|
38
|
+
readonly questionVersion: string;
|
|
39
|
+
readonly at: string;
|
|
40
|
+
}
|
|
41
|
+
export interface CollectionProposal {
|
|
42
|
+
readonly id: string;
|
|
43
|
+
/** Exact runtime-rendered review the confirmation binds to. */
|
|
44
|
+
readonly review: string;
|
|
45
|
+
/** Opaque prepared-tool continuation released to the runtime only after confirmation. */
|
|
46
|
+
readonly continuation: unknown;
|
|
47
|
+
readonly createdAt: string;
|
|
48
|
+
readonly expiresAt: string;
|
|
49
|
+
}
|
|
50
|
+
/** Sealed at rest: values include private fields, so this record never leaves custody unredacted. */
|
|
51
|
+
export interface CollectionLedger {
|
|
52
|
+
readonly id: string;
|
|
53
|
+
readonly bindingId: string;
|
|
54
|
+
readonly participantId: string;
|
|
55
|
+
readonly interactionId: string;
|
|
56
|
+
readonly action: string;
|
|
57
|
+
readonly phase: CollectionPhase;
|
|
58
|
+
readonly fields: Readonly<Record<string, CollectionFieldState>>;
|
|
59
|
+
readonly values: Readonly<Record<string, unknown>>;
|
|
60
|
+
readonly consent?: CollectionConsent;
|
|
61
|
+
readonly proposal?: CollectionProposal;
|
|
62
|
+
/** The proposal that was executed, kept after completion so its native confirmation echoes the same receipt. */
|
|
63
|
+
readonly executedProposalId?: string;
|
|
64
|
+
readonly createdAt: string;
|
|
65
|
+
readonly updatedAt: string;
|
|
66
|
+
readonly expiresAt: string;
|
|
67
|
+
}
|
|
68
|
+
export type CaptureOutcome = Readonly<Record<string, {
|
|
69
|
+
readonly status: 'captured';
|
|
70
|
+
readonly value: unknown;
|
|
71
|
+
} | {
|
|
72
|
+
readonly status: 'invalid' | 'ambiguous';
|
|
73
|
+
readonly reason: string;
|
|
74
|
+
}>>;
|
|
75
|
+
export declare class CollectionError extends Error {
|
|
76
|
+
readonly code: string;
|
|
77
|
+
constructor(code: string);
|
|
78
|
+
}
|
|
79
|
+
export declare function openCollection(spec: CollectionSpec, input: {
|
|
80
|
+
readonly id: string;
|
|
81
|
+
readonly bindingId: string;
|
|
82
|
+
readonly participantId: string;
|
|
83
|
+
readonly now: number;
|
|
84
|
+
readonly retentionMs?: number;
|
|
85
|
+
}): CollectionLedger;
|
|
86
|
+
/** Non-consent fields a capture turn may still fill or repair. */
|
|
87
|
+
export declare function neededFields(spec: CollectionSpec, ledger: CollectionLedger): readonly CollectionField[];
|
|
88
|
+
export declare function applyCapture(spec: CollectionSpec, ledger: CollectionLedger, outcome: CaptureOutcome, now: number): CollectionLedger;
|
|
89
|
+
/** The user said a stored value is wrong; keep it so a partial correction can be applied. */
|
|
90
|
+
export declare function rejectField(spec: CollectionSpec, ledger: CollectionLedger, key: string, now: number): CollectionLedger;
|
|
91
|
+
export declare function applyConsent(spec: CollectionSpec, ledger: CollectionLedger, decision: 'given' | 'declined', questionVersion: string, now: number): CollectionLedger;
|
|
92
|
+
export declare function attachProposal(spec: CollectionSpec, ledger: CollectionLedger, proposal: {
|
|
93
|
+
readonly id: string;
|
|
94
|
+
readonly review: string;
|
|
95
|
+
readonly continuation: unknown;
|
|
96
|
+
}, now: number): CollectionLedger;
|
|
97
|
+
export declare function pendingProposal(ledger: CollectionLedger, now: number): CollectionProposal | undefined;
|
|
98
|
+
export declare function applyConfirmation(ledger: CollectionLedger, act: 'confirm' | 'cancel', now: number): CollectionLedger;
|
|
99
|
+
export declare function completeCollection(ledger: CollectionLedger, now: number): CollectionLedger;
|
|
100
|
+
/** The participant asked to change something before saying what: the proposal and its acts lapse, every value stays. */
|
|
101
|
+
export declare function withdrawProposal(ledger: CollectionLedger, now: number): CollectionLedger;
|
|
102
|
+
export declare function cancelCollection(ledger: CollectionLedger, now: number): CollectionLedger;
|
|
103
|
+
/** Returns the same ledger when nothing has expired, so callers can detect a change by identity. */
|
|
104
|
+
export declare function expireCollection(ledger: CollectionLedger, now: number): CollectionLedger;
|
|
105
|
+
//# sourceMappingURL=collection-ledger.d.ts.map
|