@noodleseed/one 0.170.0 → 0.171.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +3 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/catalog.d.ts.map +1 -1
  5. package/dist/commands/catalog.js +2 -1
  6. package/dist/commands/catalog.js.map +1 -1
  7. package/dist/commands/whatsapp-ops.d.ts +13 -0
  8. package/dist/commands/whatsapp-ops.d.ts.map +1 -0
  9. package/dist/commands/whatsapp-ops.js +244 -0
  10. package/dist/commands/whatsapp-ops.js.map +1 -0
  11. package/dist/commands/whatsapp-output.d.ts +3 -0
  12. package/dist/commands/whatsapp-output.d.ts.map +1 -0
  13. package/dist/commands/whatsapp-output.js +70 -0
  14. package/dist/commands/whatsapp-output.js.map +1 -0
  15. package/dist/dev.js +2 -0
  16. package/dist/dev.js.map +1 -1
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/plugin-mode/plugin-operation-contract.d.ts +1 -1
  22. package/node_modules/@noodle-borg/agent-kit/dist/curated/command-groups.js +4 -0
  23. package/node_modules/@noodle-borg/agent-kit/dist/curated/error-fixes.js +1 -0
  24. package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +1 -1
  25. package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js +3 -0
  26. package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +6 -0
  27. package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
  28. package/node_modules/@noodle-borg/app-audit/dist/audit-embedded-assistant.js +2 -0
  29. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-configuration.js +2 -1
  30. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-model-context.js +9 -4
  31. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-turn-context.d.ts +16 -0
  32. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-turn-context.js +19 -0
  33. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-coordinator.d.ts +145 -0
  34. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-coordinator.js +378 -0
  35. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-inbox.js +160 -0
  36. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-provider-blocks.d.ts +18 -0
  37. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-provider-blocks.js +69 -0
  38. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-spend.js +82 -0
  39. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-store.d.ts +42 -0
  40. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-store.js +76 -0
  41. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-types.d.ts +134 -0
  42. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-types.js +72 -0
  43. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-work.d.ts +20 -0
  44. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-work.js +221 -0
  45. package/node_modules/@noodle-borg/assistant-gateway/dist/messaging-tool-dispatch.d.ts +15 -0
  46. package/node_modules/@noodle-borg/assistant-gateway/dist/messaging-tool-dispatch.js +34 -0
  47. package/node_modules/@noodle-borg/assistant-gateway/dist/model-request.d.ts +19 -1
  48. package/node_modules/@noodle-borg/assistant-gateway/dist/model-request.js +22 -6
  49. package/node_modules/@noodle-borg/assistant-gateway/dist/portable.d.ts +7 -0
  50. package/node_modules/@noodle-borg/assistant-gateway/dist/portable.js +7 -0
  51. package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-assistant-appearance-store.js +83 -0
  52. package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-assistant-elevation.js +151 -0
  53. package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-assistant-interaction-writes.js +45 -0
  54. package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-assistant-interactions.js +340 -0
  55. package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-assistant.js +439 -0
  56. package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-channel-store.js +102 -0
  57. package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-continuity-store.js +159 -0
  58. package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-elevation-store.js +110 -0
  59. package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-embed-store.js +134 -0
  60. package/node_modules/@noodle-borg/assistant-gateway/dist/postgres.js +11 -0
  61. package/node_modules/@noodle-borg/assistant-gateway/dist/public-surface.d.ts +8 -0
  62. package/node_modules/@noodle-borg/assistant-gateway/dist/public-surface.js +23 -0
  63. package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +17 -2
  64. package/node_modules/@noodle-borg/authoring/dist/assistant.js +48 -19
  65. package/node_modules/@noodle-borg/authoring/dist/index.d.ts +1 -1
  66. package/node_modules/@noodle-borg/authoring/dist/index.js +1 -1
  67. package/node_modules/@noodle-borg/authoring/dist/load-authored.js +1 -1
  68. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-channels.js +121 -0
  69. package/node_modules/@noodle-borg/cli-catalog/dist/index.js +1 -0
  70. package/node_modules/@noodle-borg/compiler/dist/artifact/config.js +1 -1
  71. package/node_modules/@noodle-borg/compiler/dist/errors.d.ts +1 -1
  72. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +72 -12
  73. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +51 -27
  74. package/node_modules/@noodle-borg/compiler/dist/manifest/website-projection.d.ts +1 -0
  75. package/node_modules/@noodle-borg/compiler/dist/manifest/website-projection.js +19 -0
  76. package/node_modules/@noodle-borg/module-audit/dist/index.js +37 -0
  77. package/node_modules/@noodle-borg/module-audit/dist/postgres-audit.js +126 -0
  78. package/node_modules/@noodle-borg/module-audit/dist/route.js +66 -0
  79. package/node_modules/@noodle-borg/runtime/dist/managed-origins.js +8 -4
  80. package/node_modules/@noodle-borg/runtime/dist/postgres-state-handles.js +311 -0
  81. package/node_modules/@noodle-borg/runtime/dist/postgres.js +4 -0
  82. package/node_modules/@noodle-borg/service/dist/assistant-composition.js +18 -0
  83. package/node_modules/@noodle-borg/service/dist/build-info.js +5 -2
  84. package/node_modules/@noodle-borg/service/dist/business-information/source-ingestion-coordinator.js +7 -0
  85. package/node_modules/@noodle-borg/service/dist/channels/360dialog.js +233 -0
  86. package/node_modules/@noodle-borg/service/dist/channels/cipher.js +37 -0
  87. package/node_modules/@noodle-borg/service/dist/channels/composition.js +37 -0
  88. package/node_modules/@noodle-borg/service/dist/channels/inference-guard.js +53 -0
  89. package/node_modules/@noodle-borg/service/dist/channels/runtime.js +337 -0
  90. package/node_modules/@noodle-borg/service/dist/channels/worker-loop.js +50 -0
  91. package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +62 -34
  92. package/node_modules/@noodle-borg/service/dist/routes/assistant-model-binding.js +6 -2
  93. package/node_modules/@noodle-borg/service/dist/routes/business-information-channels.js +14 -1
  94. package/node_modules/@noodle-borg/service/dist/routes/whatsapp.js +238 -0
  95. package/node_modules/@noodle-borg/service/dist/service.js +30 -32
  96. package/node_modules/@noodle-borg/service/package.json +1 -1
  97. package/node_modules/@noodle-borg/wire-contracts/dist/application-channels.d.ts +33 -0
  98. package/node_modules/@noodle-borg/wire-contracts/dist/application-channels.js +9 -0
  99. package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +1 -0
  100. package/node_modules/@noodle-borg/wire-contracts/dist/index.js +1 -0
  101. package/node_modules/@noodle-borg/wire-contracts/dist/service-info.d.ts +2 -0
  102. package/node_modules/@noodle-borg/wire-contracts/dist/service-info.js +10 -2
  103. package/node_modules/@noodle-borg/wire-contracts/dist/whatsapp.d.ts +426 -0
  104. package/node_modules/@noodle-borg/wire-contracts/dist/whatsapp.js +213 -0
  105. package/package.json +2 -2
@@ -0,0 +1,66 @@
1
+ export function createAuditEventsRoute(store) {
2
+ return {
3
+ id: 'audit.events',
4
+ match: (method, url) => method === 'GET' && url.pathname === '/v1/audit/events',
5
+ handle: async (req, res, ctx) => {
6
+ if (ctx.controlPlane === undefined) {
7
+ return sendJson(res, 401, { error: 'control-plane authentication required' });
8
+ }
9
+ const auth = await ctx.controlPlane.authorize(req);
10
+ if (!auth.ok) {
11
+ return sendJson(res, auth.status, { error: auth.message });
12
+ }
13
+ if (auth.identity === undefined) {
14
+ return sendJson(res, 401, { error: 'identity required' });
15
+ }
16
+ if (!auth.identity.superAdmin) {
17
+ return sendJson(res, 403, { error: 'forbidden' });
18
+ }
19
+ const url = new URL(req.url ?? '/', 'http://localhost');
20
+ const org = url.searchParams.get('org');
21
+ if (org === null || org.length === 0) {
22
+ return sendJson(res, 400, { error: 'org query parameter is required' });
23
+ }
24
+ try {
25
+ const filter = auditFilterFromQuery(url, org);
26
+ const events = await store.list(filter);
27
+ return sendJson(res, 200, { ok: true, events });
28
+ }
29
+ catch (error) {
30
+ return sendJson(res, 400, { error: error.message });
31
+ }
32
+ },
33
+ };
34
+ }
35
+ function auditFilterFromQuery(url, org) {
36
+ const app = optionalQuery(url, 'app');
37
+ const env = optionalQuery(url, 'env');
38
+ const eventType = optionalQuery(url, 'eventType');
39
+ const limit = parseLimit(optionalQuery(url, 'limit'));
40
+ return {
41
+ org,
42
+ ...(app !== undefined ? { app } : {}),
43
+ ...(env !== undefined ? { env } : {}),
44
+ ...(eventType !== undefined ? { eventType } : {}),
45
+ ...(limit !== undefined ? { limit } : {}),
46
+ };
47
+ }
48
+ function optionalQuery(url, key) {
49
+ const value = url.searchParams.get(key);
50
+ return value !== null && value.length > 0 ? value : undefined;
51
+ }
52
+ function parseLimit(value) {
53
+ if (value === undefined)
54
+ return undefined;
55
+ const parsed = Number(value);
56
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > 500) {
57
+ throw new Error('limit must be an integer from 1 to 500');
58
+ }
59
+ return parsed;
60
+ }
61
+ function sendJson(res, status, body) {
62
+ res.statusCode = status;
63
+ res.setHeader('content-type', 'application/json');
64
+ res.end(JSON.stringify(body));
65
+ }
66
+ //# sourceMappingURL=route.js.map
@@ -14,10 +14,12 @@ export function resolveManagedOrigins(artifact, variables, options = {}) {
14
14
  ...(assistant.surfaces === undefined
15
15
  ? {}
16
16
  : {
17
- surfaces: assistant.surfaces.map((surface, index) => ({
18
- ...surface,
19
- origins: resolveList(surface.origins, `server.assistant.surfaces.${index}.origins`, true),
20
- })),
17
+ surfaces: assistant.surfaces.map((surface, index) => surface.kind === 'messaging'
18
+ ? surface
19
+ : {
20
+ ...surface,
21
+ origins: resolveList(surface.origins, `server.assistant.surfaces.${index}.origins`, true),
22
+ }),
21
23
  }),
22
24
  };
23
25
  const handoff = artifact.server.handoff;
@@ -29,6 +31,8 @@ export function resolveManagedOrigins(artifact, variables, options = {}) {
29
31
  };
30
32
  const owners = new Map();
31
33
  resolvedAssistant?.surfaces?.forEach((surface, index) => {
34
+ if (surface.kind === 'messaging')
35
+ return;
32
36
  for (const origin of surface.origins) {
33
37
  const prior = owners.get(origin);
34
38
  if (prior !== undefined && prior !== index)
@@ -0,0 +1,311 @@
1
+ import { assertExpectedStateRevision, assertNoSecretValue, assertSchemaValue, assertStateHandleNotCompleted, createMutableStateHandleRecord, stateHandleRecordForMutation, toPublicStateHandleRecord, } from './state-handles.js';
2
+ /** Covers a request admitted just before the anonymous session's absolute expiry. */
3
+ const STATE_OWNER_REDIRECT_GRACE_MS = 5 * 60 * 1000;
4
+ export class PostgresStateHandleStore {
5
+ #pool;
6
+ #deploymentId;
7
+ #state;
8
+ #now;
9
+ constructor(pool, options) {
10
+ this.#pool = pool;
11
+ this.#deploymentId = options.deploymentId;
12
+ this.#state = options.state;
13
+ this.#now = options.now ?? (() => new Date());
14
+ }
15
+ async read(input) {
16
+ const def = this.#definition(input.handle);
17
+ const owner = ownerKey(def, input);
18
+ const key = input.key ?? 'default';
19
+ const read = async (client) => {
20
+ const nowDate = this.#now();
21
+ const resolvedOwner = def.claimOnAuthentication === true
22
+ ? await resolveOperationOwner(client, this.#deploymentId, input.handle, owner, nowDate)
23
+ : owner;
24
+ const row = await selectRow(client, this.#deploymentId, input.handle, resolvedOwner, key);
25
+ const now = nowDate.getTime();
26
+ if (row === undefined) {
27
+ return toPublicStateHandleRecord(createMutableStateHandleRecord(input.handle, key, def.ttlSeconds, now), now);
28
+ }
29
+ assertVersion(row, def);
30
+ return toPublicStateHandleRecord(mutableFromRow(row), now);
31
+ };
32
+ return def.claimOnAuthentication === true
33
+ ? this.#withTransaction((client) => read(client))
34
+ : read(this.#pool);
35
+ }
36
+ async patch(input) {
37
+ return this.#withTransaction(async (client) => {
38
+ const def = this.#definition(input.handle);
39
+ const owner = ownerKey(def, input);
40
+ const key = input.key ?? 'default';
41
+ const nowDate = this.#now();
42
+ const resolvedOwner = def.claimOnAuthentication === true
43
+ ? await resolveOperationOwner(client, this.#deploymentId, input.handle, owner, nowDate)
44
+ : owner;
45
+ const row = await selectRow(client, this.#deploymentId, input.handle, resolvedOwner, key, true);
46
+ if (row !== undefined)
47
+ assertVersion(row, def);
48
+ const now = nowDate.getTime();
49
+ const current = stateHandleRecordForMutation(row ? mutableFromRow(row) : undefined, input.handle, key, def.ttlSeconds, now);
50
+ assertStateHandleNotCompleted(current);
51
+ assertExpectedStateRevision(current, input.expectedRevision);
52
+ assertNoSecretValue(input.value);
53
+ const value = { ...current.value, ...input.value };
54
+ assertSchemaValue(def.schema, value);
55
+ const next = {
56
+ ...current,
57
+ value,
58
+ revision: current.revision + 1,
59
+ updatedAt: now,
60
+ };
61
+ await upsertRow(client, this.#deploymentId, resolvedOwner, def.version, next);
62
+ return toPublicStateHandleRecord(next, now);
63
+ });
64
+ }
65
+ async complete(input) {
66
+ return this.#withTransaction(async (client) => {
67
+ const def = this.#definition(input.handle);
68
+ const owner = ownerKey(def, input);
69
+ const key = input.key ?? 'default';
70
+ const nowDate = this.#now();
71
+ const resolvedOwner = def.claimOnAuthentication === true
72
+ ? await resolveOperationOwner(client, this.#deploymentId, input.handle, owner, nowDate)
73
+ : owner;
74
+ const row = await selectRow(client, this.#deploymentId, input.handle, resolvedOwner, key, true);
75
+ if (row !== undefined)
76
+ assertVersion(row, def);
77
+ const now = nowDate.getTime();
78
+ const current = stateHandleRecordForMutation(row ? mutableFromRow(row) : undefined, input.handle, key, def.ttlSeconds, now);
79
+ assertStateHandleNotCompleted(current);
80
+ assertExpectedStateRevision(current, input.expectedRevision);
81
+ const next = {
82
+ ...current,
83
+ status: 'completed',
84
+ revision: current.revision + 1,
85
+ updatedAt: now,
86
+ };
87
+ await upsertRow(client, this.#deploymentId, resolvedOwner, def.version, next);
88
+ return toPublicStateHandleRecord(next, now);
89
+ });
90
+ }
91
+ async pruneExpired() {
92
+ const result = await this.#pool.query(`DELETE FROM state_handle_records
93
+ WHERE deployment_id = $1 AND expires_at IS NOT NULL AND expires_at <= $2`, [this.#deploymentId, this.#now()]);
94
+ await this.#pool.query(`DELETE FROM state_handle_owner_redirects
95
+ WHERE deployment_id = $1 AND expires_at <= $2`, [this.#deploymentId, new Date(this.#now().getTime() - STATE_OWNER_REDIRECT_GRACE_MS)]);
96
+ return result.rowCount ?? 0;
97
+ }
98
+ #definition(handle) {
99
+ const def = this.#state.handles[handle];
100
+ if (def === undefined)
101
+ throw new Error(`unknown state handle "${handle}"`);
102
+ return def;
103
+ }
104
+ async #withTransaction(work) {
105
+ const client = await this.#pool.connect();
106
+ try {
107
+ await client.query('BEGIN');
108
+ const result = await work(client);
109
+ await client.query('COMMIT');
110
+ return result;
111
+ }
112
+ catch (err) {
113
+ await client.query('ROLLBACK');
114
+ throw err;
115
+ }
116
+ finally {
117
+ client.release();
118
+ }
119
+ }
120
+ }
121
+ export async function ensureStateHandleSchema(pool) {
122
+ await pool.query(`
123
+ CREATE TABLE IF NOT EXISTS state_handle_records (
124
+ deployment_id text NOT NULL,
125
+ handle_name text NOT NULL,
126
+ owner_key text NOT NULL,
127
+ state_key text NOT NULL,
128
+ handle_version text NOT NULL,
129
+ value jsonb NOT NULL,
130
+ revision bigint NOT NULL,
131
+ status text NOT NULL CHECK (status IN ('active', 'completed')),
132
+ created_at timestamptz NOT NULL,
133
+ updated_at timestamptz NOT NULL,
134
+ expires_at timestamptz,
135
+ PRIMARY KEY (deployment_id, handle_name, owner_key, state_key)
136
+ )
137
+ `);
138
+ await pool.query(`
139
+ CREATE INDEX IF NOT EXISTS state_handle_records_expiry_idx
140
+ ON state_handle_records (expires_at)
141
+ WHERE expires_at IS NOT NULL
142
+ `);
143
+ await pool.query(`
144
+ CREATE TABLE IF NOT EXISTS state_handle_owner_redirects (
145
+ deployment_id text NOT NULL,
146
+ handle_name text NOT NULL,
147
+ source_owner_key text NOT NULL,
148
+ target_owner_key text NOT NULL,
149
+ created_at timestamptz NOT NULL,
150
+ expires_at timestamptz NOT NULL,
151
+ PRIMARY KEY (deployment_id, handle_name, source_owner_key),
152
+ CHECK (source_owner_key <> target_owner_key)
153
+ )
154
+ `);
155
+ await pool.query(`
156
+ CREATE INDEX IF NOT EXISTS state_handle_owner_redirects_expiry_idx
157
+ ON state_handle_owner_redirects (expires_at)
158
+ `);
159
+ }
160
+ /**
161
+ * Rebind opted-in caller state inside the elevation transaction supplied by assistant-gateway.
162
+ * Values are never decoded or rewritten: changing only `owner_key` preserves every lifecycle field.
163
+ */
164
+ export async function adoptCallerState(client, input) {
165
+ if (input.sourceCallerSubject === input.targetCallerSubject) {
166
+ return { ok: true, adoptedHandles: [], adoptedRecords: 0 };
167
+ }
168
+ const handles = [...new Set(input.handles)].sort();
169
+ for (const handle of handles) {
170
+ // Anonymous -> authenticated is directional. Every opted state operation takes the source
171
+ // lock before following its redirect and then takes the target lock, so the adoption uses the
172
+ // same order while still serializing concurrent creates under either owner.
173
+ await lockOwner(client, input.deploymentId, handle, input.sourceCallerSubject);
174
+ await lockOwner(client, input.deploymentId, handle, input.targetCallerSubject);
175
+ }
176
+ if (handles.length === 0) {
177
+ return { ok: true, adoptedHandles: [], adoptedRecords: 0 };
178
+ }
179
+ const redirect = await client.query(`SELECT handle_name, target_owner_key
180
+ FROM state_handle_owner_redirects
181
+ WHERE deployment_id = $1
182
+ AND handle_name = ANY($2::text[])
183
+ AND source_owner_key = $3
184
+ AND expires_at > $4`, [
185
+ input.deploymentId,
186
+ handles,
187
+ input.sourceCallerSubject,
188
+ new Date(input.now.getTime() - STATE_OWNER_REDIRECT_GRACE_MS),
189
+ ]);
190
+ if (redirect.rows.some((row) => row.target_owner_key !== input.targetCallerSubject)) {
191
+ return { ok: false, reason: 'state_key_conflict' };
192
+ }
193
+ const collision = await client.query(`SELECT 1
194
+ FROM state_handle_records AS source
195
+ JOIN state_handle_records AS target
196
+ ON target.deployment_id = source.deployment_id
197
+ AND target.handle_name = source.handle_name
198
+ AND target.state_key = source.state_key
199
+ WHERE source.deployment_id = $1
200
+ AND source.handle_name = ANY($2::text[])
201
+ AND source.owner_key = $3
202
+ AND target.owner_key = $4
203
+ LIMIT 1`, [input.deploymentId, handles, input.sourceCallerSubject, input.targetCallerSubject]);
204
+ if ((collision.rowCount ?? 0) > 0) {
205
+ return { ok: false, reason: 'state_key_conflict' };
206
+ }
207
+ const moved = await client.query(`UPDATE state_handle_records
208
+ SET owner_key = $4
209
+ WHERE deployment_id = $1
210
+ AND handle_name = ANY($2::text[])
211
+ AND owner_key = $3`, [input.deploymentId, handles, input.sourceCallerSubject, input.targetCallerSubject]);
212
+ await client.query(`INSERT INTO state_handle_owner_redirects
213
+ (deployment_id, handle_name, source_owner_key, target_owner_key, created_at, expires_at)
214
+ SELECT $1, handle_name, $3, $4, $5, $6
215
+ FROM unnest($2::text[]) AS handle_name
216
+ ON CONFLICT (deployment_id, handle_name, source_owner_key) DO UPDATE SET
217
+ target_owner_key = EXCLUDED.target_owner_key,
218
+ created_at = EXCLUDED.created_at,
219
+ expires_at = EXCLUDED.expires_at`, [
220
+ input.deploymentId,
221
+ handles,
222
+ input.sourceCallerSubject,
223
+ input.targetCallerSubject,
224
+ input.now,
225
+ input.redirectExpiresAt,
226
+ ]);
227
+ return {
228
+ ok: true,
229
+ adoptedHandles: handles,
230
+ adoptedRecords: moved.rowCount ?? 0,
231
+ };
232
+ }
233
+ async function selectRow(client, deploymentId, handle, owner, key, forUpdate = false) {
234
+ const result = await client.query(`SELECT handle_name, state_key, handle_version, value, revision, status,
235
+ created_at, updated_at, expires_at
236
+ FROM state_handle_records
237
+ WHERE deployment_id = $1 AND handle_name = $2 AND owner_key = $3 AND state_key = $4
238
+ ${forUpdate ? 'FOR UPDATE' : ''}`, [deploymentId, handle, owner, key]);
239
+ return result.rows[0];
240
+ }
241
+ async function upsertRow(client, deploymentId, owner, version, record) {
242
+ await client.query(`INSERT INTO state_handle_records
243
+ (deployment_id, handle_name, owner_key, state_key, handle_version, value, revision,
244
+ status, created_at, updated_at, expires_at)
245
+ VALUES ($1, $2, $3, $4, $5, $6::jsonb, $7, $8, $9, $10, $11)
246
+ ON CONFLICT (deployment_id, handle_name, owner_key, state_key) DO UPDATE SET
247
+ handle_version = EXCLUDED.handle_version,
248
+ value = EXCLUDED.value,
249
+ revision = EXCLUDED.revision,
250
+ status = EXCLUDED.status,
251
+ created_at = EXCLUDED.created_at,
252
+ updated_at = EXCLUDED.updated_at,
253
+ expires_at = EXCLUDED.expires_at`, [
254
+ deploymentId,
255
+ record.handle,
256
+ owner,
257
+ record.key,
258
+ version,
259
+ JSON.stringify(record.value),
260
+ record.revision,
261
+ record.status,
262
+ new Date(record.createdAt),
263
+ new Date(record.updatedAt),
264
+ record.expiresAt === undefined ? null : new Date(record.expiresAt),
265
+ ]);
266
+ }
267
+ function ownerKey(def, input) {
268
+ return def.scope === 'caller' ? (input.callerSubject ?? 'anonymous') : 'deployment';
269
+ }
270
+ async function lockOwner(client, deploymentId, handle, owner) {
271
+ await client.query('SELECT pg_advisory_xact_lock(hashtextextended($1, 0))', [
272
+ stateOwnerLockKey(deploymentId, handle, owner),
273
+ ]);
274
+ }
275
+ /** PostgreSQL text rejects NUL, so length-prefix each untrusted segment instead of using a sentinel. */
276
+ function stateOwnerLockKey(deploymentId, handle, owner) {
277
+ return `state-owner:${deploymentId.length}:${deploymentId}:${handle.length}:${handle}:${owner.length}:${owner}`;
278
+ }
279
+ async function resolveOperationOwner(client, deploymentId, handle, owner, now) {
280
+ await lockOwner(client, deploymentId, handle, owner);
281
+ const redirect = await client.query(`SELECT target_owner_key
282
+ FROM state_handle_owner_redirects
283
+ WHERE deployment_id = $1 AND handle_name = $2 AND source_owner_key = $3 AND expires_at > $4`, [deploymentId, handle, owner, new Date(now.getTime() - STATE_OWNER_REDIRECT_GRACE_MS)]);
284
+ const target = redirect.rows[0]?.target_owner_key;
285
+ if (target === undefined)
286
+ return owner;
287
+ await lockOwner(client, deploymentId, handle, target);
288
+ return target;
289
+ }
290
+ function assertVersion(row, def) {
291
+ if (row.handle_version !== def.version) {
292
+ throw new Error(`state handle version mismatch for "${row.handle_name}": stored ${row.handle_version}, current ${def.version}`);
293
+ }
294
+ }
295
+ function mutableFromRow(row) {
296
+ const value = typeof row.value === 'object' && row.value !== null && !Array.isArray(row.value)
297
+ ? row.value
298
+ : {};
299
+ const expiresAt = row.expires_at === null ? undefined : new Date(row.expires_at).getTime();
300
+ return {
301
+ handle: row.handle_name,
302
+ key: row.state_key,
303
+ value,
304
+ revision: Number(row.revision),
305
+ status: row.status,
306
+ createdAt: new Date(row.created_at).getTime(),
307
+ updatedAt: new Date(row.updated_at).getTime(),
308
+ ...(expiresAt !== undefined ? { expiresAt } : {}),
309
+ };
310
+ }
311
+ //# sourceMappingURL=postgres-state-handles.js.map
@@ -0,0 +1,4 @@
1
+ /** PostgreSQL state-handle persistence for hosted composition. */
2
+ export * from './postgres-state-handles.js';
3
+ export * from './state-handle-ownership.js';
4
+ //# sourceMappingURL=postgres.js.map
@@ -0,0 +1,18 @@
1
+ /** Browser and messaging resolve model, execution and tenant policy from one composition. */
2
+ export function assistantRouteDependencies(options, core) {
3
+ return {
4
+ ...core,
5
+ ...(options.publicEmbeds ? { publicEmbeds: options.publicEmbeds } : {}),
6
+ ...(options.elevations ? { elevations: options.elevations } : {}),
7
+ ...(options.elevationCoordinator ? { elevationCoordinator: options.elevationCoordinator } : {}),
8
+ ...(options.admissionCounters ? { admissionCounters: options.admissionCounters } : {}),
9
+ ...(options.admissionEnvelope ? { admissionEnvelope: options.admissionEnvelope } : {}),
10
+ ...(options.assistantModelFetch ? { modelFetch: options.assistantModelFetch } : {}),
11
+ ...(options.managedAssistantModelResolver
12
+ ? { managedModelResolver: options.managedAssistantModelResolver }
13
+ : {}),
14
+ ...(options.captureRequestEvent ? { captureRequestEvent: options.captureRequestEvent } : {}),
15
+ ...(options.clock ? { clock: options.clock } : {}),
16
+ };
17
+ }
18
+ //# sourceMappingURL=assistant-composition.js.map
@@ -1,10 +1,13 @@
1
1
  import { MIXED_CUSTOMER_AUTH_FEATURE_VERSION, serviceInfoResponseSchema, } from '@noodle-borg/wire-contracts';
2
- export function serviceInfoPayload(buildInfo, developerMcp) {
2
+ export function serviceInfoPayload(buildInfo, developerMcp, whatsapp = false) {
3
3
  return serviceInfoResponseSchema.parse({
4
4
  ok: true,
5
5
  status: 'ok',
6
6
  ...buildInfo,
7
- features: { mixedCustomerAuth: MIXED_CUSTOMER_AUTH_FEATURE_VERSION },
7
+ features: {
8
+ mixedCustomerAuth: MIXED_CUSTOMER_AUTH_FEATURE_VERSION,
9
+ ...(whatsapp ? { whatsapp: 1 } : {}),
10
+ },
8
11
  ...(developerMcp
9
12
  ? { developerPlugin: { mcpCapabilityVersion: DEVELOPER_MCP_CAPABILITY_VERSION } }
10
13
  : {}),
@@ -133,4 +133,11 @@ function boundedToken(label, value, maximum) {
133
133
  }
134
134
  return normalized;
135
135
  }
136
+ export async function drainSourceIngestion(coordinator) {
137
+ for (let attempt = 0; attempt < 100; attempt += 1) {
138
+ const result = await coordinator.runOne();
139
+ if (result.disposition !== 'completed')
140
+ return;
141
+ }
142
+ }
136
143
  //# sourceMappingURL=source-ingestion-coordinator.js.map