@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.
- 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/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/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,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
|
-
|
|
19
|
-
|
|
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,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: {
|
|
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
|
: {}),
|
package/node_modules/@noodle-borg/service/dist/business-information/source-ingestion-coordinator.js
CHANGED
|
@@ -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
|