@noodleseed/one 0.170.0 → 0.172.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/catalog.d.ts.map +1 -1
- package/dist/commands/catalog.js +2 -1
- package/dist/commands/catalog.js.map +1 -1
- package/dist/commands/solutions-connections.js +1 -1
- package/dist/commands/whatsapp-ops.d.ts +13 -0
- package/dist/commands/whatsapp-ops.d.ts.map +1 -0
- package/dist/commands/whatsapp-ops.js +244 -0
- package/dist/commands/whatsapp-ops.js.map +1 -0
- package/dist/commands/whatsapp-output.d.ts +3 -0
- package/dist/commands/whatsapp-output.d.ts.map +1 -0
- package/dist/commands/whatsapp-output.js +70 -0
- package/dist/commands/whatsapp-output.js.map +1 -0
- package/dist/dev.js +2 -0
- package/dist/dev.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/plugin-mode/plugin-operation-contract.d.ts +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/curated/command-groups.js +4 -0
- package/node_modules/@noodle-borg/agent-kit/dist/curated/error-fixes.js +1 -0
- package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js +3 -0
- package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +6 -0
- package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
- package/node_modules/@noodle-borg/app-audit/dist/audit-embedded-assistant.js +2 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-configuration.js +2 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-model-context.js +9 -4
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-turn-context.d.ts +16 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-turn-context.js +19 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-coordinator.d.ts +145 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-coordinator.js +378 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-inbox.js +160 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-provider-blocks.d.ts +18 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-provider-blocks.js +69 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-spend.js +82 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-store.d.ts +42 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-store.js +76 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-types.d.ts +134 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-types.js +72 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-work.d.ts +20 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-work.js +221 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/messaging-tool-dispatch.d.ts +15 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/messaging-tool-dispatch.js +34 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/model-request.d.ts +19 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/model-request.js +22 -6
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.d.ts +7 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.js +7 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-assistant-appearance-store.js +83 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-assistant-elevation.js +151 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-assistant-interaction-writes.js +45 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-assistant-interactions.js +340 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-assistant.js +439 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-channel-store.js +102 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-continuity-store.js +159 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-elevation-store.js +110 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-embed-store.js +134 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/postgres.js +11 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-surface.d.ts +8 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-surface.js +23 -0
- package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +17 -2
- package/node_modules/@noodle-borg/authoring/dist/assistant.js +48 -19
- package/node_modules/@noodle-borg/authoring/dist/index.d.ts +1 -1
- package/node_modules/@noodle-borg/authoring/dist/index.js +1 -1
- package/node_modules/@noodle-borg/authoring/dist/load-authored.js +1 -1
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-channels.js +121 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solutions.js +1 -1
- package/node_modules/@noodle-borg/cli-catalog/dist/index.js +1 -0
- package/node_modules/@noodle-borg/compiler/dist/artifact/config.js +1 -1
- package/node_modules/@noodle-borg/compiler/dist/errors.d.ts +1 -1
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +72 -12
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +51 -27
- package/node_modules/@noodle-borg/compiler/dist/manifest/website-projection.d.ts +1 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/website-projection.js +19 -0
- package/node_modules/@noodle-borg/module-audit/dist/index.js +37 -0
- package/node_modules/@noodle-borg/module-audit/dist/postgres-audit.js +126 -0
- package/node_modules/@noodle-borg/module-audit/dist/route.js +66 -0
- package/node_modules/@noodle-borg/runtime/dist/managed-origins.js +8 -4
- package/node_modules/@noodle-borg/runtime/dist/postgres-state-handles.js +311 -0
- package/node_modules/@noodle-borg/runtime/dist/postgres.js +4 -0
- package/node_modules/@noodle-borg/service/dist/assistant-composition.js +18 -0
- package/node_modules/@noodle-borg/service/dist/build-info.js +5 -2
- package/node_modules/@noodle-borg/service/dist/business-information/source-ingestion-coordinator.js +7 -0
- package/node_modules/@noodle-borg/service/dist/channels/360dialog.js +233 -0
- package/node_modules/@noodle-borg/service/dist/channels/cipher.js +37 -0
- package/node_modules/@noodle-borg/service/dist/channels/composition.js +37 -0
- package/node_modules/@noodle-borg/service/dist/channels/inference-guard.js +53 -0
- package/node_modules/@noodle-borg/service/dist/channels/runtime.js +337 -0
- package/node_modules/@noodle-borg/service/dist/channels/worker-loop.js +50 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +62 -34
- package/node_modules/@noodle-borg/service/dist/routes/assistant-model-binding.js +6 -2
- package/node_modules/@noodle-borg/service/dist/routes/business-information-channels.js +14 -1
- package/node_modules/@noodle-borg/service/dist/routes/whatsapp.js +238 -0
- package/node_modules/@noodle-borg/service/dist/service.js +30 -32
- 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 +9 -0
- 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 +10 -2
- package/node_modules/@noodle-borg/wire-contracts/dist/whatsapp.d.ts +426 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/whatsapp.js +213 -0
- package/package.json +2 -2
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { continuityDigest, continuityHandle, continuityMaxRestores, continuityWindowMs, } from './continuity-store.js';
|
|
3
|
+
function toRecord(row) {
|
|
4
|
+
return {
|
|
5
|
+
id: row.id,
|
|
6
|
+
sessionId: row.session_id,
|
|
7
|
+
tenant: { org: row.org_slug, app: row.app_slug, env: row.environment },
|
|
8
|
+
context: {
|
|
9
|
+
embedId: row.embed_id,
|
|
10
|
+
originHash: row.origin_hash,
|
|
11
|
+
visitorHash: row.visitor_hash,
|
|
12
|
+
},
|
|
13
|
+
handleHash: row.handle_hash,
|
|
14
|
+
restoreCount: row.restore_count,
|
|
15
|
+
maxRestores: row.max_restores,
|
|
16
|
+
createdAt: row.created_at.toISOString(),
|
|
17
|
+
expiresAt: row.expires_at.toISOString(),
|
|
18
|
+
...(row.claimed_at === null ? {} : { claimedAt: row.claimed_at.toISOString() }),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Durable continuity handles (ADR 0223, clauses 11-16).
|
|
23
|
+
*
|
|
24
|
+
* Its own table, deliberately not a second row kind in `assistant_elevations`. A display-only handle and
|
|
25
|
+
* an identity-transition ticket must not share storage, so that a defect in either cannot reach the
|
|
26
|
+
* other and a reader never has to work out which rows are dangerous.
|
|
27
|
+
*
|
|
28
|
+
* Two properties are enforced by the database rather than by application order, exactly as they are for
|
|
29
|
+
* elevations. A **partial unique index over unclaimed rows** keeps at most one live handle per session,
|
|
30
|
+
* so each turn's handle supersedes rather than accumulating one live key per turn. And the claim is a
|
|
31
|
+
* **single statement** — `UPDATE … WHERE claimed_at IS NULL … RETURNING` — because two backends racing
|
|
32
|
+
* one handle is precisely the case a read-then-write loses, and the loser would restore a second time.
|
|
33
|
+
*
|
|
34
|
+
* The refusal reasons are distinguished by re-reading the row *without* consuming it: a wrong-context
|
|
35
|
+
* probe must not spend the rightful visitor's handle, or reading someone's conversation becomes
|
|
36
|
+
* unnecessary and denying it becomes trivial.
|
|
37
|
+
*/
|
|
38
|
+
export class PostgresAssistantContinuityStore {
|
|
39
|
+
#pool;
|
|
40
|
+
constructor(pool) {
|
|
41
|
+
this.#pool = pool;
|
|
42
|
+
}
|
|
43
|
+
async ensureSchema() {
|
|
44
|
+
await this.#pool.query(`
|
|
45
|
+
CREATE TABLE IF NOT EXISTS assistant_continuity_handles (
|
|
46
|
+
id text PRIMARY KEY,
|
|
47
|
+
session_id text NOT NULL,
|
|
48
|
+
org_slug text NOT NULL,
|
|
49
|
+
app_slug text NOT NULL,
|
|
50
|
+
environment text NOT NULL,
|
|
51
|
+
embed_id text NOT NULL,
|
|
52
|
+
origin_hash text NOT NULL,
|
|
53
|
+
visitor_hash text NOT NULL,
|
|
54
|
+
handle_hash text NOT NULL,
|
|
55
|
+
restore_count integer NOT NULL DEFAULT 0,
|
|
56
|
+
max_restores integer NOT NULL,
|
|
57
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
58
|
+
expires_at timestamptz NOT NULL,
|
|
59
|
+
claimed_at timestamptz
|
|
60
|
+
)
|
|
61
|
+
`);
|
|
62
|
+
await this.#pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS assistant_continuity_handle_key
|
|
63
|
+
ON assistant_continuity_handles (handle_hash)`);
|
|
64
|
+
await this.#pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS assistant_continuity_live_session_key
|
|
65
|
+
ON assistant_continuity_handles (session_id) WHERE claimed_at IS NULL`);
|
|
66
|
+
// Retention is part of this store rather than an afterthought: handles are short-lived and must be
|
|
67
|
+
// swept, not accumulated, and the sweep needs this index to stay cheap as the table grows.
|
|
68
|
+
await this.#pool.query(`CREATE INDEX IF NOT EXISTS assistant_continuity_expires_at_idx
|
|
69
|
+
ON assistant_continuity_handles (expires_at)`);
|
|
70
|
+
}
|
|
71
|
+
async issue(input) {
|
|
72
|
+
return this.#write({ ...input, restoreCount: 0 });
|
|
73
|
+
}
|
|
74
|
+
async claim(input) {
|
|
75
|
+
const hash = continuityDigest(input.handle);
|
|
76
|
+
const { rows } = await this.#pool.query(`UPDATE assistant_continuity_handles
|
|
77
|
+
SET claimed_at = $2, restore_count = restore_count + 1
|
|
78
|
+
WHERE handle_hash = $1
|
|
79
|
+
AND claimed_at IS NULL
|
|
80
|
+
AND expires_at > $2
|
|
81
|
+
AND embed_id = $3 AND origin_hash = $4 AND visitor_hash = $5
|
|
82
|
+
RETURNING *`, [
|
|
83
|
+
hash,
|
|
84
|
+
input.now.toISOString(),
|
|
85
|
+
input.context.embedId,
|
|
86
|
+
input.context.originHash,
|
|
87
|
+
input.context.visitorHash,
|
|
88
|
+
]);
|
|
89
|
+
const claimed = rows[0];
|
|
90
|
+
if (!claimed)
|
|
91
|
+
return this.#explainRefusal(hash, input.context);
|
|
92
|
+
const record = toRecord(claimed);
|
|
93
|
+
if (record.restoreCount >= record.maxRestores)
|
|
94
|
+
return { ok: true, record };
|
|
95
|
+
const rotated = await this.#write({
|
|
96
|
+
sessionId: record.sessionId,
|
|
97
|
+
tenant: record.tenant,
|
|
98
|
+
context: record.context,
|
|
99
|
+
// Re-derive from the record so a rotation can never widen what the first issue clamped.
|
|
100
|
+
windowMs: Date.parse(record.expiresAt) - Date.parse(record.createdAt),
|
|
101
|
+
maxRestores: record.maxRestores,
|
|
102
|
+
restoreCount: record.restoreCount,
|
|
103
|
+
now: input.now,
|
|
104
|
+
});
|
|
105
|
+
return rotated === undefined
|
|
106
|
+
? { ok: true, record }
|
|
107
|
+
: { ok: true, record, handle: rotated.handle };
|
|
108
|
+
}
|
|
109
|
+
/** Remove spent and expired rows. Callers may lag expiry: `claim` already refuses a stale handle. */
|
|
110
|
+
async sweepExpired(input) {
|
|
111
|
+
const { rowCount } = await this.#pool.query('DELETE FROM assistant_continuity_handles WHERE expires_at <= $1 OR claimed_at IS NOT NULL', [input.now.toISOString()]);
|
|
112
|
+
return rowCount ?? 0;
|
|
113
|
+
}
|
|
114
|
+
async #explainRefusal(hash, context) {
|
|
115
|
+
const { rows } = await this.#pool.query('SELECT * FROM assistant_continuity_handles WHERE handle_hash = $1', [hash]);
|
|
116
|
+
const row = rows[0];
|
|
117
|
+
if (!row || row.claimed_at !== null)
|
|
118
|
+
return { ok: false, reason: 'unknown' };
|
|
119
|
+
if (row.embed_id !== context.embedId ||
|
|
120
|
+
row.origin_hash !== context.originHash ||
|
|
121
|
+
row.visitor_hash !== context.visitorHash) {
|
|
122
|
+
return { ok: false, reason: 'context_mismatch' };
|
|
123
|
+
}
|
|
124
|
+
return { ok: false, reason: 'expired' };
|
|
125
|
+
}
|
|
126
|
+
async #write(input) {
|
|
127
|
+
const windowMs = continuityWindowMs(input.windowMs);
|
|
128
|
+
const maxRestores = continuityMaxRestores(input.maxRestores);
|
|
129
|
+
if (windowMs === 0 || maxRestores === 0)
|
|
130
|
+
return undefined;
|
|
131
|
+
// Delete before insert so the partial unique index cannot reject the supersede.
|
|
132
|
+
await this.#pool.query('DELETE FROM assistant_continuity_handles WHERE session_id = $1 AND claimed_at IS NULL', [input.sessionId]);
|
|
133
|
+
const handle = continuityHandle();
|
|
134
|
+
const { rows } = await this.#pool.query(`INSERT INTO assistant_continuity_handles
|
|
135
|
+
(id, session_id, org_slug, app_slug, environment, embed_id, origin_hash, visitor_hash,
|
|
136
|
+
handle_hash, restore_count, max_restores, created_at, expires_at)
|
|
137
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
|
|
138
|
+
RETURNING *`, [
|
|
139
|
+
`cont_${randomUUID().replaceAll('-', '')}`,
|
|
140
|
+
input.sessionId,
|
|
141
|
+
input.tenant.org,
|
|
142
|
+
input.tenant.app,
|
|
143
|
+
input.tenant.env,
|
|
144
|
+
input.context.embedId,
|
|
145
|
+
input.context.originHash,
|
|
146
|
+
input.context.visitorHash,
|
|
147
|
+
continuityDigest(handle),
|
|
148
|
+
input.restoreCount,
|
|
149
|
+
maxRestores,
|
|
150
|
+
input.now.toISOString(),
|
|
151
|
+
new Date(input.now.getTime() + windowMs).toISOString(),
|
|
152
|
+
]);
|
|
153
|
+
const row = rows[0];
|
|
154
|
+
if (!row)
|
|
155
|
+
throw new Error('continuity insert returned no row');
|
|
156
|
+
return { record: toRecord(row), handle };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=postgres-continuity-store.js.map
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { ASSISTANT_ELEVATION_TTL_MS, elevationContinuation, elevationDigest, } from './elevation-store.js';
|
|
3
|
+
function toRecord(row) {
|
|
4
|
+
return {
|
|
5
|
+
id: row.id,
|
|
6
|
+
sessionId: row.session_id,
|
|
7
|
+
tenant: { org: row.org_slug, app: row.app_slug, env: row.environment },
|
|
8
|
+
tool: row.tool,
|
|
9
|
+
claimableStateHandles: [...(row.claimable_state_handles ?? [])],
|
|
10
|
+
continuationHash: row.continuation_hash,
|
|
11
|
+
createdAt: row.created_at.toISOString(),
|
|
12
|
+
expiresAt: row.expires_at.toISOString(),
|
|
13
|
+
...(row.claimed_at === null ? {} : { claimedAt: row.claimed_at.toISOString() }),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Durable elevation continuations.
|
|
18
|
+
*
|
|
19
|
+
* Two properties are load-bearing and both are enforced by the database rather than by application
|
|
20
|
+
* order. A **partial unique index over unclaimed rows** keeps at most one live elevation per session, so
|
|
21
|
+
* a model asking twice supersedes rather than accumulating keys to the same conversation. And the claim
|
|
22
|
+
* is a **single statement** — `UPDATE … WHERE claimed_at IS NULL … RETURNING` — because two backends
|
|
23
|
+
* racing the same continuation is exactly the case a read-then-write loses, and the loser would elevate
|
|
24
|
+
* a second time.
|
|
25
|
+
*
|
|
26
|
+
* The refusal reasons are distinguished by re-reading the row *without* consuming it: a tenant mismatch
|
|
27
|
+
* must not spend the rightful owner's continuation, or a probe becomes a denial of service.
|
|
28
|
+
*/
|
|
29
|
+
export class PostgresAssistantElevationStore {
|
|
30
|
+
#pool;
|
|
31
|
+
constructor(pool) {
|
|
32
|
+
this.#pool = pool;
|
|
33
|
+
}
|
|
34
|
+
async ensureSchema() {
|
|
35
|
+
await this.#pool.query(`
|
|
36
|
+
CREATE TABLE IF NOT EXISTS assistant_elevations (
|
|
37
|
+
id text PRIMARY KEY,
|
|
38
|
+
session_id text NOT NULL,
|
|
39
|
+
org_slug text NOT NULL,
|
|
40
|
+
app_slug text NOT NULL,
|
|
41
|
+
environment text NOT NULL,
|
|
42
|
+
tool text NOT NULL,
|
|
43
|
+
claimable_state_handles jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
44
|
+
continuation_hash text NOT NULL,
|
|
45
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
46
|
+
expires_at timestamptz NOT NULL,
|
|
47
|
+
claimed_at timestamptz
|
|
48
|
+
)
|
|
49
|
+
`);
|
|
50
|
+
await this.#pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS assistant_elevations_continuation_key
|
|
51
|
+
ON assistant_elevations (continuation_hash)`);
|
|
52
|
+
await this.#pool.query(`
|
|
53
|
+
ALTER TABLE assistant_elevations
|
|
54
|
+
ADD COLUMN IF NOT EXISTS claimable_state_handles jsonb NOT NULL DEFAULT '[]'::jsonb
|
|
55
|
+
`);
|
|
56
|
+
await this.#pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS assistant_elevations_live_session_key
|
|
57
|
+
ON assistant_elevations (session_id) WHERE claimed_at IS NULL`);
|
|
58
|
+
}
|
|
59
|
+
async request(input) {
|
|
60
|
+
// Delete before insert so the partial unique index cannot reject the supersede.
|
|
61
|
+
await this.#pool.query('DELETE FROM assistant_elevations WHERE session_id = $1 AND claimed_at IS NULL', [input.sessionId]);
|
|
62
|
+
const continuation = elevationContinuation();
|
|
63
|
+
const { rows } = await this.#pool.query(`INSERT INTO assistant_elevations
|
|
64
|
+
(id, session_id, org_slug, app_slug, environment, tool, claimable_state_handles,
|
|
65
|
+
continuation_hash, created_at, expires_at)
|
|
66
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb, $8, $9, $10)
|
|
67
|
+
RETURNING *`, [
|
|
68
|
+
`elev_${randomUUID().replaceAll('-', '')}`,
|
|
69
|
+
input.sessionId,
|
|
70
|
+
input.tenant.org,
|
|
71
|
+
input.tenant.app,
|
|
72
|
+
input.tenant.env,
|
|
73
|
+
input.tool,
|
|
74
|
+
JSON.stringify([...(input.claimableStateHandles ?? [])].sort()),
|
|
75
|
+
elevationDigest(continuation),
|
|
76
|
+
input.now.toISOString(),
|
|
77
|
+
new Date(input.now.getTime() + ASSISTANT_ELEVATION_TTL_MS).toISOString(),
|
|
78
|
+
]);
|
|
79
|
+
const row = rows[0];
|
|
80
|
+
if (!row)
|
|
81
|
+
throw new Error('elevation insert returned no row');
|
|
82
|
+
return { elevation: toRecord(row), continuation };
|
|
83
|
+
}
|
|
84
|
+
async claim(input) {
|
|
85
|
+
const hash = elevationDigest(input.continuation);
|
|
86
|
+
const { rows } = await this.#pool.query(`UPDATE assistant_elevations
|
|
87
|
+
SET claimed_at = $2
|
|
88
|
+
WHERE continuation_hash = $1
|
|
89
|
+
AND claimed_at IS NULL
|
|
90
|
+
AND expires_at > $2
|
|
91
|
+
AND org_slug = $3 AND app_slug = $4 AND environment = $5
|
|
92
|
+
RETURNING *`, [hash, input.now.toISOString(), input.tenant.org, input.tenant.app, input.tenant.env]);
|
|
93
|
+
const claimed = rows[0];
|
|
94
|
+
if (claimed)
|
|
95
|
+
return { ok: true, elevation: toRecord(claimed) };
|
|
96
|
+
// Nothing was spent. Re-read to say *why* without consuming anything, so a wrong-tenant probe
|
|
97
|
+
// cannot deny the rightful owner their elevation.
|
|
98
|
+
const { rows: existing } = await this.#pool.query('SELECT * FROM assistant_elevations WHERE continuation_hash = $1', [hash]);
|
|
99
|
+
const row = existing[0];
|
|
100
|
+
if (!row || row.claimed_at !== null)
|
|
101
|
+
return { ok: false, reason: 'unknown' };
|
|
102
|
+
if (row.org_slug !== input.tenant.org ||
|
|
103
|
+
row.app_slug !== input.tenant.app ||
|
|
104
|
+
row.environment !== input.tenant.env) {
|
|
105
|
+
return { ok: false, reason: 'tenant_mismatch' };
|
|
106
|
+
}
|
|
107
|
+
return { ok: false, reason: 'expired' };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=postgres-elevation-store.js.map
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { newPublicEmbedId } from './in-memory-embed-store.js';
|
|
2
|
+
function toRecord(row) {
|
|
3
|
+
return {
|
|
4
|
+
embedId: row.embed_id,
|
|
5
|
+
org: row.org_slug,
|
|
6
|
+
app: row.app_slug,
|
|
7
|
+
env: row.environment,
|
|
8
|
+
surfaceMode: row.surface_mode,
|
|
9
|
+
createdAt: row.created_at,
|
|
10
|
+
...(row.revoked_at === null ? {} : { revokedAt: row.revoked_at }),
|
|
11
|
+
// `bigint` arrives as a string; null stays undefined so "no override" and "zero" never merge.
|
|
12
|
+
...(row.turns_per_day === null ? {} : { turnsPerDay: Number(row.turns_per_day) }),
|
|
13
|
+
...(row.mints_per_day === null ? {} : { mintsPerDay: Number(row.mints_per_day) }),
|
|
14
|
+
...(row.mints_per_address_hour === null
|
|
15
|
+
? {}
|
|
16
|
+
: { mintsPerAddressHour: Number(row.mints_per_address_hour) }),
|
|
17
|
+
...(row.turns_per_address_hour === null
|
|
18
|
+
? {}
|
|
19
|
+
: { turnsPerAddressHour: Number(row.turns_per_address_hour) }),
|
|
20
|
+
...(row.bridge_tool_calls_per_session === null
|
|
21
|
+
? {}
|
|
22
|
+
: { bridgeToolCallsPerSession: Number(row.bridge_tool_calls_per_session) }),
|
|
23
|
+
...(row.bridge_tool_calls_per_day === null
|
|
24
|
+
? {}
|
|
25
|
+
: { bridgeToolCallsPerDay: Number(row.bridge_tool_calls_per_day) }),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Durable public embed identifiers.
|
|
30
|
+
*
|
|
31
|
+
* The table has **no secret column** — see `embed-store.ts` for why that is the point rather than an
|
|
32
|
+
* omission. A partial unique index over live rows is what makes `ensure` idempotent under concurrency:
|
|
33
|
+
* two simultaneous deploys of the same tenant surface cannot mint two live ids, and a revoked row does
|
|
34
|
+
* not block a later one, so revoke-then-redeploy issues a genuinely new id instead of reviving the old.
|
|
35
|
+
*/
|
|
36
|
+
export class PostgresPublicEmbedStore {
|
|
37
|
+
#pool;
|
|
38
|
+
constructor(pool) {
|
|
39
|
+
this.#pool = pool;
|
|
40
|
+
}
|
|
41
|
+
async ensureSchema() {
|
|
42
|
+
await this.#pool.query(`
|
|
43
|
+
CREATE TABLE IF NOT EXISTS assistant_public_embeds (
|
|
44
|
+
embed_id text PRIMARY KEY,
|
|
45
|
+
org_slug text NOT NULL,
|
|
46
|
+
app_slug text NOT NULL,
|
|
47
|
+
environment text NOT NULL,
|
|
48
|
+
surface_mode text NOT NULL,
|
|
49
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
50
|
+
revoked_at timestamptz
|
|
51
|
+
)
|
|
52
|
+
`);
|
|
53
|
+
await this.#pool.query('ALTER TABLE assistant_public_embeds ADD COLUMN IF NOT EXISTS turns_per_day bigint');
|
|
54
|
+
await this.#pool.query('ALTER TABLE assistant_public_embeds ADD COLUMN IF NOT EXISTS mints_per_day bigint');
|
|
55
|
+
await this.#pool.query('ALTER TABLE assistant_public_embeds ADD COLUMN IF NOT EXISTS mints_per_address_hour bigint');
|
|
56
|
+
await this.#pool.query('ALTER TABLE assistant_public_embeds ADD COLUMN IF NOT EXISTS turns_per_address_hour bigint');
|
|
57
|
+
// The bridge budgets became operator-settable after they shipped platform-only (ADR 0220);
|
|
58
|
+
// additive like every column above it, so an existing row simply keeps using the defaults.
|
|
59
|
+
await this.#pool.query('ALTER TABLE assistant_public_embeds ADD COLUMN IF NOT EXISTS bridge_tool_calls_per_session bigint');
|
|
60
|
+
await this.#pool.query('ALTER TABLE assistant_public_embeds ADD COLUMN IF NOT EXISTS bridge_tool_calls_per_day bigint');
|
|
61
|
+
await this.#pool.query(`
|
|
62
|
+
CREATE UNIQUE INDEX IF NOT EXISTS assistant_public_embeds_live
|
|
63
|
+
ON assistant_public_embeds (org_slug, app_slug, environment)
|
|
64
|
+
WHERE revoked_at IS NULL
|
|
65
|
+
`);
|
|
66
|
+
}
|
|
67
|
+
async ensure(input) {
|
|
68
|
+
// `DO NOTHING` plus a follow-up read rather than `DO UPDATE`: a redeploy must return the *existing*
|
|
69
|
+
// id untouched, including its original `created_at`, because that id is already in page source.
|
|
70
|
+
const inserted = await this.#pool.query(`INSERT INTO assistant_public_embeds
|
|
71
|
+
(embed_id, org_slug, app_slug, environment, surface_mode, created_at)
|
|
72
|
+
SELECT $1, $2, $3, $4, $5, $6
|
|
73
|
+
WHERE $7::boolean OR NOT EXISTS (SELECT 1 FROM assistant_public_embeds
|
|
74
|
+
WHERE org_slug = $2 AND app_slug = $3 AND environment = $4)
|
|
75
|
+
ON CONFLICT DO NOTHING
|
|
76
|
+
RETURNING *`, [
|
|
77
|
+
newPublicEmbedId(),
|
|
78
|
+
input.org,
|
|
79
|
+
input.app,
|
|
80
|
+
input.env,
|
|
81
|
+
input.surfaceMode,
|
|
82
|
+
input.now,
|
|
83
|
+
input.allowRevokedReplacement !== false,
|
|
84
|
+
]);
|
|
85
|
+
const row = inserted.rows[0];
|
|
86
|
+
if (row !== undefined)
|
|
87
|
+
return toRecord(row);
|
|
88
|
+
const existing = await this.#pool.query(`SELECT * FROM assistant_public_embeds
|
|
89
|
+
WHERE org_slug = $1 AND app_slug = $2 AND environment = $3 AND revoked_at IS NULL`, [input.org, input.app, input.env]);
|
|
90
|
+
const found = existing.rows[0];
|
|
91
|
+
if (found === undefined)
|
|
92
|
+
throw new Error('public embed id could not be resolved after insert');
|
|
93
|
+
return toRecord(found);
|
|
94
|
+
}
|
|
95
|
+
async setBudget(embedId, budget, _now) {
|
|
96
|
+
// COALESCE on the parameter, not the column: passing null leaves that cap as it was, so raising
|
|
97
|
+
// turns cannot quietly clear a mint ceiling someone set separately.
|
|
98
|
+
const result = await this.#pool.query(`UPDATE assistant_public_embeds
|
|
99
|
+
SET turns_per_day = COALESCE($2::bigint, turns_per_day),
|
|
100
|
+
mints_per_day = COALESCE($3::bigint, mints_per_day),
|
|
101
|
+
mints_per_address_hour = COALESCE($4::bigint, mints_per_address_hour),
|
|
102
|
+
turns_per_address_hour = COALESCE($5::bigint, turns_per_address_hour),
|
|
103
|
+
bridge_tool_calls_per_session = COALESCE($6::bigint, bridge_tool_calls_per_session),
|
|
104
|
+
bridge_tool_calls_per_day = COALESCE($7::bigint, bridge_tool_calls_per_day)
|
|
105
|
+
WHERE embed_id = $1 AND revoked_at IS NULL
|
|
106
|
+
RETURNING *`, [
|
|
107
|
+
embedId,
|
|
108
|
+
budget.turnsPerDay ?? null,
|
|
109
|
+
budget.mintsPerDay ?? null,
|
|
110
|
+
budget.mintsPerAddressHour ?? null,
|
|
111
|
+
budget.turnsPerAddressHour ?? null,
|
|
112
|
+
budget.bridgeToolCallsPerSession ?? null,
|
|
113
|
+
budget.bridgeToolCallsPerDay ?? null,
|
|
114
|
+
]);
|
|
115
|
+
const row = result.rows[0];
|
|
116
|
+
return row === undefined ? undefined : toRecord(row);
|
|
117
|
+
}
|
|
118
|
+
async lookup(embedId) {
|
|
119
|
+
const result = await this.#pool.query('SELECT * FROM assistant_public_embeds WHERE embed_id = $1 AND revoked_at IS NULL', [embedId]);
|
|
120
|
+
const row = result.rows[0];
|
|
121
|
+
return row === undefined ? undefined : toRecord(row);
|
|
122
|
+
}
|
|
123
|
+
async list(tenant, options) {
|
|
124
|
+
const result = await this.#pool.query(`SELECT * FROM assistant_public_embeds
|
|
125
|
+
WHERE org_slug = $1 AND app_slug = $2 AND environment = $3 AND ($4::boolean OR revoked_at IS NULL)
|
|
126
|
+
ORDER BY created_at`, [tenant.org, tenant.app, tenant.env, options?.includeRevoked === true]);
|
|
127
|
+
return result.rows.map(toRecord);
|
|
128
|
+
}
|
|
129
|
+
async revoke(embedId, now) {
|
|
130
|
+
const result = await this.#pool.query('UPDATE assistant_public_embeds SET revoked_at = $2 WHERE embed_id = $1 AND revoked_at IS NULL', [embedId, now]);
|
|
131
|
+
return (result.rowCount ?? 0) > 0;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=postgres-embed-store.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** PostgreSQL assistant adapters for hosted service composition only. */
|
|
2
|
+
export * from './postgres-assistant.js';
|
|
3
|
+
export * from './postgres-assistant-appearance-store.js';
|
|
4
|
+
export * from './postgres-assistant-elevation.js';
|
|
5
|
+
export * from './postgres-assistant-interaction-writes.js';
|
|
6
|
+
export * from './postgres-assistant-interactions.js';
|
|
7
|
+
export * from './postgres-channel-store.js';
|
|
8
|
+
export * from './postgres-continuity-store.js';
|
|
9
|
+
export * from './postgres-elevation-store.js';
|
|
10
|
+
export * from './postgres-embed-store.js';
|
|
11
|
+
//# sourceMappingURL=postgres.js.map
|
|
@@ -39,4 +39,12 @@ export declare function surfaceBindingForOrigin(assistant: unknown, origin: stri
|
|
|
39
39
|
readonly kind: 'pre-surfaces';
|
|
40
40
|
};
|
|
41
41
|
export declare function publicSurfaceOf(assistant: unknown): PublicSurface | undefined;
|
|
42
|
+
export interface MessagingSurface {
|
|
43
|
+
readonly kind: 'messaging';
|
|
44
|
+
readonly channel: 'whatsapp';
|
|
45
|
+
readonly mode: 'public';
|
|
46
|
+
readonly capabilities: readonly SurfaceCapabilityRef[];
|
|
47
|
+
readonly instructions?: string;
|
|
48
|
+
}
|
|
49
|
+
export declare function messagingSurfaceOf(assistant: unknown): MessagingSurface | undefined;
|
|
42
50
|
//# sourceMappingURL=public-surface.d.ts.map
|
|
@@ -4,6 +4,8 @@ export function authenticatedSurfaceOf(assistant) {
|
|
|
4
4
|
if (!Array.isArray(surfaces))
|
|
5
5
|
return undefined;
|
|
6
6
|
for (const entry of surfaces) {
|
|
7
|
+
if (entry?.kind === 'messaging')
|
|
8
|
+
continue;
|
|
7
9
|
const { mode, origins, capabilities, instructions } = entry;
|
|
8
10
|
if (mode !== 'authenticated')
|
|
9
11
|
continue;
|
|
@@ -42,6 +44,8 @@ export function publicSurfaceOf(assistant) {
|
|
|
42
44
|
if (!Array.isArray(surfaces))
|
|
43
45
|
return undefined;
|
|
44
46
|
for (const entry of surfaces) {
|
|
47
|
+
if (entry?.kind === 'messaging')
|
|
48
|
+
continue;
|
|
45
49
|
const { mode, origins, capabilities, instructions } = entry;
|
|
46
50
|
// A mixed surface admits strangers exactly as a public one does — sign-in only widens what a visitor
|
|
47
51
|
// reaches after elevation, so both provision an embed id and both project.
|
|
@@ -61,4 +65,23 @@ export function publicSurfaceOf(assistant) {
|
|
|
61
65
|
}
|
|
62
66
|
return undefined;
|
|
63
67
|
}
|
|
68
|
+
export function messagingSurfaceOf(assistant) {
|
|
69
|
+
const surfaces = assistant?.surfaces;
|
|
70
|
+
if (!Array.isArray(surfaces))
|
|
71
|
+
return undefined;
|
|
72
|
+
for (const entry of surfaces) {
|
|
73
|
+
if (entry?.kind !== 'messaging' || entry.channel !== 'whatsapp' || entry.mode !== 'public')
|
|
74
|
+
continue;
|
|
75
|
+
if (!Array.isArray(entry.capabilities))
|
|
76
|
+
return undefined;
|
|
77
|
+
return {
|
|
78
|
+
kind: 'messaging',
|
|
79
|
+
channel: 'whatsapp',
|
|
80
|
+
mode: 'public',
|
|
81
|
+
capabilities: entry.capabilities,
|
|
82
|
+
...(typeof entry.instructions === 'string' ? { instructions: entry.instructions } : {}),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
64
87
|
//# sourceMappingURL=public-surface.js.map
|
|
@@ -162,6 +162,7 @@ export interface AssistantCapability {
|
|
|
162
162
|
*/
|
|
163
163
|
export type AssistantSurfaceMode = 'authenticated' | 'public' | 'mixed';
|
|
164
164
|
export interface AuthenticatedWebsiteAccess {
|
|
165
|
+
readonly kind?: 'website';
|
|
165
166
|
readonly mode: 'authenticated';
|
|
166
167
|
readonly origins: readonly string[];
|
|
167
168
|
readonly instructions?: string;
|
|
@@ -199,6 +200,7 @@ export interface AssistantContinuityDeclaration {
|
|
|
199
200
|
readonly maxRestores?: number;
|
|
200
201
|
}
|
|
201
202
|
export interface PublicWebsiteAccess {
|
|
203
|
+
readonly kind?: 'website';
|
|
202
204
|
readonly mode: 'public' | 'mixed';
|
|
203
205
|
readonly origins: readonly string[];
|
|
204
206
|
readonly capabilities: readonly CapabilityRef[];
|
|
@@ -210,7 +212,19 @@ export interface PublicWebsiteAccess {
|
|
|
210
212
|
/** @see PublicWebsiteInput.continuity */
|
|
211
213
|
readonly continuity?: AssistantContinuityDeclaration;
|
|
212
214
|
}
|
|
213
|
-
export
|
|
215
|
+
export interface PublicMessagingInput {
|
|
216
|
+
readonly channel: 'whatsapp';
|
|
217
|
+
readonly capabilities: readonly CapabilityRef[];
|
|
218
|
+
readonly instructions?: string;
|
|
219
|
+
}
|
|
220
|
+
export interface PublicMessagingAccess extends Omit<PublicMessagingInput, 'capabilities'> {
|
|
221
|
+
readonly capabilities: readonly AssistantCapability[];
|
|
222
|
+
readonly kind: 'messaging';
|
|
223
|
+
readonly mode: 'public';
|
|
224
|
+
}
|
|
225
|
+
/** Public business capability intent. Provider assets and credentials are operator-owned. */
|
|
226
|
+
export declare function publicMessaging(input: PublicMessagingInput): PublicMessagingAccess;
|
|
227
|
+
export type AssistantAccess = AuthenticatedWebsiteAccess | PublicWebsiteAccess | PublicMessagingAccess;
|
|
214
228
|
export interface AuthenticatedWebsiteInput {
|
|
215
229
|
readonly origins: readonly (string | ConfigRef)[];
|
|
216
230
|
/** Assistant-only behavior for this exact signed-in website audience. */
|
|
@@ -281,6 +295,7 @@ export interface EmbeddedAssistantOptions extends AssistantUiOptions {
|
|
|
281
295
|
}
|
|
282
296
|
/** One projected surface as it appears in compiled data. */
|
|
283
297
|
export interface AssistantSurfaceConfig {
|
|
298
|
+
readonly kind?: 'website';
|
|
284
299
|
readonly mode: AssistantSurfaceMode;
|
|
285
300
|
readonly origins: readonly string[];
|
|
286
301
|
readonly instructions?: string;
|
|
@@ -299,7 +314,7 @@ export interface AssistantSurfaceConfig {
|
|
|
299
314
|
}
|
|
300
315
|
export interface EmbeddedAssistantConfig extends AssistantUiOptions {
|
|
301
316
|
readonly model: AssistantModel;
|
|
302
|
-
readonly surfaces: readonly AssistantSurfaceConfig[];
|
|
317
|
+
readonly surfaces: readonly (AssistantSurfaceConfig | PublicMessagingAccess)[];
|
|
303
318
|
/**
|
|
304
319
|
* Union of every surface's origins. Kept at the top level so the service's origin check and the
|
|
305
320
|
* published session-exchange contract read on unchanged while surfaces are added.
|
|
@@ -1,4 +1,24 @@
|
|
|
1
1
|
import { serializeSecretRef, serializeVariableRef } from './config.js';
|
|
2
|
+
/** Public business capability intent. Provider assets and credentials are operator-owned. */
|
|
3
|
+
export function publicMessaging(input) {
|
|
4
|
+
if (!input || !Array.isArray(input.capabilities)) {
|
|
5
|
+
throw new Error('publicMessaging requires an explicit capability allowlist');
|
|
6
|
+
}
|
|
7
|
+
for (const key of Object.keys(input)) {
|
|
8
|
+
if (!['channel', 'capabilities', 'instructions'].includes(key)) {
|
|
9
|
+
throw new Error(`publicMessaging does not accept ${key}`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
if (input.channel !== 'whatsapp')
|
|
13
|
+
throw new Error('Unsupported public messaging channel');
|
|
14
|
+
return {
|
|
15
|
+
kind: 'messaging',
|
|
16
|
+
mode: 'public',
|
|
17
|
+
channel: input.channel,
|
|
18
|
+
capabilities: normalizeCapabilities(input.capabilities),
|
|
19
|
+
...(input.instructions === undefined ? {} : { instructions: input.instructions }),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
2
22
|
export function authenticatedWebsite(input) {
|
|
3
23
|
return {
|
|
4
24
|
mode: 'authenticated',
|
|
@@ -49,22 +69,27 @@ export function embeddedAssistant(input) {
|
|
|
49
69
|
const sessionClaims = surfaces.find((surface) => surface.mode === 'authenticated')?.sessionClaims;
|
|
50
70
|
return {
|
|
51
71
|
model: { ...model },
|
|
52
|
-
surfaces: surfaces.map((surface) =>
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
:
|
|
59
|
-
|
|
60
|
-
? {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
surfaces: surfaces.map((surface) => surface.kind === 'messaging'
|
|
73
|
+
? {
|
|
74
|
+
...surface,
|
|
75
|
+
capabilities: normalizeCapabilities(surface.capabilities),
|
|
76
|
+
}
|
|
77
|
+
: {
|
|
78
|
+
mode: surface.mode,
|
|
79
|
+
origins: [...surface.origins],
|
|
80
|
+
...(surface.instructions === undefined ? {} : { instructions: surface.instructions }),
|
|
81
|
+
...(surface.capabilities
|
|
82
|
+
? { capabilities: normalizeCapabilities(surface.capabilities) }
|
|
83
|
+
: {}),
|
|
84
|
+
...(surface.mode === 'authenticated' && surface.sessionClaims
|
|
85
|
+
? { sessionClaims: structuredClone(surface.sessionClaims) }
|
|
86
|
+
: {}),
|
|
87
|
+
...(surface.webmcp === undefined ? {} : { webmcp: { ...surface.webmcp } }),
|
|
88
|
+
...(surface.mode !== 'authenticated' && surface.continuity !== undefined
|
|
89
|
+
? { continuity: { ...surface.continuity } }
|
|
90
|
+
: {}),
|
|
91
|
+
}),
|
|
92
|
+
allowedOrigins: surfaces.flatMap((surface) => surface.kind === 'messaging' ? [] : [...surface.origins]),
|
|
68
93
|
...structuredClone(ui),
|
|
69
94
|
...(suggestedPrompts ? { suggestedPrompts: [...suggestedPrompts] } : {}),
|
|
70
95
|
...(sessionClaims ? { sessionClaims: structuredClone(sessionClaims) } : {}),
|
|
@@ -109,15 +134,19 @@ function assertDistinctSurfaces(surfaces) {
|
|
|
109
134
|
if (surfaces.length === 0) {
|
|
110
135
|
throw new Error('embeddedAssistant requires at least one access surface');
|
|
111
136
|
}
|
|
112
|
-
const
|
|
137
|
+
const websites = surfaces.filter((surface) => surface.kind !== 'messaging');
|
|
138
|
+
const messaging = surfaces.filter((surface) => surface.kind === 'messaging');
|
|
139
|
+
if (messaging.length > 1)
|
|
140
|
+
throw new Error('embeddedAssistant accepts only one whatsapp surface');
|
|
141
|
+
const publicSurfaces = websites.filter((surface) => surface.mode !== 'authenticated');
|
|
113
142
|
if (publicSurfaces.length > 1) {
|
|
114
143
|
throw new Error('embeddedAssistant accepts at most one public surface (public or mixed)');
|
|
115
144
|
}
|
|
116
|
-
if (
|
|
145
|
+
if (websites.length - publicSurfaces.length > 1) {
|
|
117
146
|
throw new Error('embeddedAssistant accepts at most one authenticated surface');
|
|
118
147
|
}
|
|
119
148
|
const seen = new Set();
|
|
120
|
-
for (const surface of
|
|
149
|
+
for (const surface of websites) {
|
|
121
150
|
for (const origin of surface.origins) {
|
|
122
151
|
if (seen.has(origin)) {
|
|
123
152
|
throw new Error(`embeddedAssistant surfaces must not share an origin: ${origin}`);
|
|
@@ -2,7 +2,7 @@ import type { ConnectorFile, HttpConnectorDef } from '@noodle-borg/connector-def
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
export type { PackagedAssetReference } from '@noodle-borg/compiler';
|
|
4
4
|
export type { AgentCapabilityKind, AgentCapabilityRef, AgentExampleSource, AgentGuideSource, AgentWorkflowSource, AgentWorkflowStepSource, } from './agent-guide.js';
|
|
5
|
-
export { type AssistantAccess, type AssistantCapability, type AssistantLabels, type AssistantLayoutMode, type AssistantModel, type AssistantPosition, type AssistantPresentationOptions, type AssistantPresentationTone, type AssistantSurfaceConfig, type AssistantSurfaceMode, type AssistantThemeMode, type AssistantUiOptions, type AuthenticatedWebsiteAccess, type AuthenticatedWebsiteInput, authenticatedWebsite, type CapabilityRef, type EmbeddedAssistantConfig, type EmbeddedAssistantOptions, embeddedAssistant, type NoodleManagedModel, noodleManaged, type OpenAICompatibleModel, type OpenAICompatibleModelInput, type OpenAICompatibleTransport, openAICompatible, type PublicWebsiteAccess, type PublicWebsiteInput, publicWebsite, } from './assistant.js';
|
|
5
|
+
export { type AssistantAccess, type AssistantCapability, type AssistantLabels, type AssistantLayoutMode, type AssistantModel, type AssistantPosition, type AssistantPresentationOptions, type AssistantPresentationTone, type AssistantSurfaceConfig, type AssistantSurfaceMode, type AssistantThemeMode, type AssistantUiOptions, type AuthenticatedWebsiteAccess, type AuthenticatedWebsiteInput, authenticatedWebsite, type CapabilityRef, type EmbeddedAssistantConfig, type EmbeddedAssistantOptions, embeddedAssistant, type NoodleManagedModel, noodleManaged, type OpenAICompatibleModel, type OpenAICompatibleModelInput, type OpenAICompatibleTransport, openAICompatible, type PublicMessagingAccess, type PublicMessagingInput, type PublicWebsiteAccess, type PublicWebsiteInput, publicMessaging, publicWebsite, } from './assistant.js';
|
|
6
6
|
export { type ConfigRef, type ConfigRefKind, type DeclaredVariableRef, secret, type VariableOptions, type VariableValueRef, variable, } from './config.js';
|
|
7
7
|
export { type BoundConnectorRef, bind, type ConnectionBinding, type ConnectionRef, type ConnectionSource, clientCredentials, connection, externalExchange, type GoogleWorkloadIdentityAccess, googleWorkloadIdentity, managedSecret, } from './connections.js';
|
|
8
8
|
export { type ComputeHost, type ComputeLimits, type ComputeOperationOptions, ConnectorBuilder, type ConnectorCatalogDoc, type ConnectorOpDefinition, type ConnectorOperationOptions, type ConnectorRef, connector, type HttpConnectorOptions, type McpAuthOptions, type McpConnectorOptions, } from './connectors.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
export { authenticatedWebsite, embeddedAssistant, noodleManaged, openAICompatible, publicWebsite, } from './assistant.js';
|
|
2
|
+
export { authenticatedWebsite, embeddedAssistant, noodleManaged, openAICompatible, publicMessaging, publicWebsite, } from './assistant.js';
|
|
3
3
|
export { secret, variable, } from './config.js';
|
|
4
4
|
export { bind, clientCredentials, connection, externalExchange, googleWorkloadIdentity, managedSecret, } from './connections.js';
|
|
5
5
|
export { ConnectorBuilder, connector, } from './connectors.js';
|
|
@@ -33,7 +33,7 @@ export function prepareAuthoringRuntimeDir(dir, options) {
|
|
|
33
33
|
'./react': './react.mjs',
|
|
34
34
|
},
|
|
35
35
|
}));
|
|
36
|
-
writeFileSync(join(self, 'index.mjs'), "export { algolia, annotations, asset, authenticatedWebsite, bind, clientCredentials, connector, connection, customerAuth, customerEndpoint, embeddedAssistant, externalExchange, file, firecrawl, gmailConnector, googleWorkloadIdentity, handoffSession, knowledge, managedCollection, managedSecret, meilisearch, noodleManaged, noodlePlatform, openAICompatible, prompt, publicWebsite, resource, secret, server, site, tavily, tool, variable, webExtract, when, z } from '@noodle-borg/authoring';\n");
|
|
36
|
+
writeFileSync(join(self, 'index.mjs'), "export { algolia, annotations, asset, authenticatedWebsite, bind, clientCredentials, connector, connection, customerAuth, customerEndpoint, embeddedAssistant, externalExchange, file, firecrawl, gmailConnector, googleWorkloadIdentity, handoffSession, knowledge, managedCollection, managedSecret, meilisearch, noodleManaged, noodlePlatform, openAICompatible, prompt, publicMessaging, publicWebsite, resource, secret, server, site, tavily, tool, variable, webExtract, when, z } from '@noodle-borg/authoring';\n");
|
|
37
37
|
writeFileSync(join(self, 'platform.mjs'), "export { noodlePlatform, noodlePlatformCatalog } from '@noodle-borg/authoring';\n");
|
|
38
38
|
writeFileSync(join(self, 'react.mjs'), "export * from '@noodle-borg/authoring/react';\n");
|
|
39
39
|
}
|