@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
package/node_modules/@noodle-borg/assistant-gateway/dist/postgres-assistant-interaction-writes.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ADMISSION_DEFAULTS } from '@noodle-borg/admission-limits/portable';
|
|
2
|
+
import { AssistantInteractionCapacityError, DEFAULT_MAX_PENDING_INTERACTIONS_PER_SESSION, } from './assistant-interaction-state.js';
|
|
3
|
+
/** Caller must hold the session advisory transaction lock before reserving a pending slot. */
|
|
4
|
+
export async function reserveAssistantInteractionPendingSlot(client, sessionId, now) {
|
|
5
|
+
await client.query(`DELETE FROM assistant_pending_tool_calls
|
|
6
|
+
WHERE session_id=$1 AND status='pending' AND expires_at <= $2`, [sessionId, now]);
|
|
7
|
+
const count = await client.query(`SELECT COUNT(*)::int AS pending_count
|
|
8
|
+
FROM assistant_pending_tool_calls
|
|
9
|
+
WHERE session_id=$1 AND status='pending' AND expires_at > $2`, [sessionId, now]);
|
|
10
|
+
// An anonymous visitor gets the public envelope's bound; a signed-in embed keeps the wider default.
|
|
11
|
+
// The two audiences genuinely differ: a stranger holding several half-finished confirmations is a
|
|
12
|
+
// way to accumulate state on a surface nobody is accountable for, which the envelope has always said
|
|
13
|
+
// is one — it simply had nothing reading it.
|
|
14
|
+
const owner = await client.query('SELECT public_embed_id FROM assistant_sessions WHERE id = $1', [sessionId]);
|
|
15
|
+
const limit = owner.rows[0]?.public_embed_id === null || owner.rows[0]?.public_embed_id === undefined
|
|
16
|
+
? DEFAULT_MAX_PENDING_INTERACTIONS_PER_SESSION
|
|
17
|
+
: ADMISSION_DEFAULTS.pendingInteractions;
|
|
18
|
+
if ((count.rows[0]?.pending_count ?? 0) >= limit) {
|
|
19
|
+
throw new AssistantInteractionCapacityError();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export async function insertAssistantPendingInteraction(client, interaction) {
|
|
23
|
+
await client.query(`INSERT INTO assistant_pending_tool_calls
|
|
24
|
+
(id, kind, status, session_id, deployment_id, tool, arguments, message,
|
|
25
|
+
requested_schema, continuation, review, invocation_context, created_at, expires_at)
|
|
26
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb,$8,$9::jsonb,$10::jsonb,$11::jsonb,$12::jsonb,$13,$14)`, [
|
|
27
|
+
interaction.id,
|
|
28
|
+
interaction.kind,
|
|
29
|
+
interaction.status,
|
|
30
|
+
interaction.sessionId,
|
|
31
|
+
interaction.deploymentId,
|
|
32
|
+
interaction.tool,
|
|
33
|
+
interaction.kind === 'confirmation' ? JSON.stringify(interaction.arguments) : null,
|
|
34
|
+
interaction.kind === 'input' ? interaction.message : null,
|
|
35
|
+
interaction.kind === 'input' ? JSON.stringify(interaction.requestedSchema) : null,
|
|
36
|
+
interaction.continuation !== undefined ? JSON.stringify(interaction.continuation) : null,
|
|
37
|
+
interaction.kind === 'confirmation' && interaction.review !== undefined
|
|
38
|
+
? JSON.stringify(interaction.review)
|
|
39
|
+
: null,
|
|
40
|
+
interaction.context ? JSON.stringify(interaction.context) : null,
|
|
41
|
+
interaction.createdAt,
|
|
42
|
+
interaction.expiresAt,
|
|
43
|
+
]);
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=postgres-assistant-interaction-writes.js.map
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { ASSISTANT_INTERACTION_EXECUTION_LIMIT_MS, ASSISTANT_INTERACTION_OUTCOME_RETENTION_MS, ASSISTANT_INTERACTION_UNKNOWN_OUTCOME, completedInteraction, createPendingInteraction, executingInteraction, expireStrandedExecutingInteraction, isPrunableInteraction, isTerminalInteraction, normalizePublicOutcome, shouldExpireStrandedExecutingInteraction, transitionedInteractions, } from './assistant-interaction-state.js';
|
|
3
|
+
import { insertAssistantPendingInteraction, reserveAssistantInteractionPendingSlot, } from './postgres-assistant-interaction-writes.js';
|
|
4
|
+
/** Logged single-executor handoff: private inputs are removed atomically before dispatch. */
|
|
5
|
+
export class PostgresAssistantInteractions {
|
|
6
|
+
#pool;
|
|
7
|
+
constructor(pool) {
|
|
8
|
+
this.#pool = pool;
|
|
9
|
+
}
|
|
10
|
+
async ensureSchema() {
|
|
11
|
+
await this.#pool.query(`
|
|
12
|
+
CREATE TABLE IF NOT EXISTS assistant_pending_tool_calls (
|
|
13
|
+
id text PRIMARY KEY,
|
|
14
|
+
kind text NOT NULL DEFAULT 'confirmation',
|
|
15
|
+
status text NOT NULL DEFAULT 'pending',
|
|
16
|
+
session_id text NOT NULL,
|
|
17
|
+
deployment_id text NOT NULL DEFAULT '',
|
|
18
|
+
tool text NOT NULL,
|
|
19
|
+
arguments jsonb,
|
|
20
|
+
message text,
|
|
21
|
+
requested_schema jsonb,
|
|
22
|
+
continuation jsonb,
|
|
23
|
+
review jsonb,
|
|
24
|
+
invocation_context jsonb,
|
|
25
|
+
created_at timestamptz NOT NULL,
|
|
26
|
+
claimed_at timestamptz,
|
|
27
|
+
completed_at timestamptz,
|
|
28
|
+
public_outcome jsonb,
|
|
29
|
+
payload_scrubbed_at timestamptz,
|
|
30
|
+
expires_at timestamptz NOT NULL
|
|
31
|
+
)
|
|
32
|
+
`);
|
|
33
|
+
// Additive rolling migration from the confirmation-only table. Legacy rows have no deployment
|
|
34
|
+
// binding and therefore fail closed when consumed by the new service.
|
|
35
|
+
await this.#pool.query(`
|
|
36
|
+
ALTER TABLE assistant_pending_tool_calls
|
|
37
|
+
ADD COLUMN IF NOT EXISTS kind text NOT NULL DEFAULT 'confirmation',
|
|
38
|
+
ADD COLUMN IF NOT EXISTS status text NOT NULL DEFAULT 'pending',
|
|
39
|
+
ADD COLUMN IF NOT EXISTS deployment_id text NOT NULL DEFAULT '',
|
|
40
|
+
ADD COLUMN IF NOT EXISTS message text,
|
|
41
|
+
ADD COLUMN IF NOT EXISTS requested_schema jsonb,
|
|
42
|
+
ADD COLUMN IF NOT EXISTS continuation jsonb,
|
|
43
|
+
ADD COLUMN IF NOT EXISTS review jsonb,
|
|
44
|
+
ADD COLUMN IF NOT EXISTS invocation_context jsonb,
|
|
45
|
+
ADD COLUMN IF NOT EXISTS created_at timestamptz,
|
|
46
|
+
ADD COLUMN IF NOT EXISTS claimed_at timestamptz,
|
|
47
|
+
ADD COLUMN IF NOT EXISTS completed_at timestamptz,
|
|
48
|
+
ADD COLUMN IF NOT EXISTS public_outcome jsonb,
|
|
49
|
+
ADD COLUMN IF NOT EXISTS payload_scrubbed_at timestamptz
|
|
50
|
+
`);
|
|
51
|
+
await this.#pool.query(`
|
|
52
|
+
UPDATE assistant_pending_tool_calls
|
|
53
|
+
SET created_at=expires_at - interval '10 minutes'
|
|
54
|
+
WHERE created_at IS NULL
|
|
55
|
+
`);
|
|
56
|
+
await this.#pool.query(`
|
|
57
|
+
ALTER TABLE assistant_pending_tool_calls
|
|
58
|
+
ALTER COLUMN created_at SET NOT NULL,
|
|
59
|
+
ALTER COLUMN arguments DROP NOT NULL
|
|
60
|
+
`);
|
|
61
|
+
// Confirmations used to live in an UNLOGGED scratch table. Outcomes and executing/unknown states
|
|
62
|
+
// must survive database restart and participate in replication, so migrate the relation in place.
|
|
63
|
+
await this.#pool.query(`
|
|
64
|
+
DO $$
|
|
65
|
+
BEGIN
|
|
66
|
+
IF EXISTS (
|
|
67
|
+
SELECT 1 FROM pg_class
|
|
68
|
+
WHERE oid='assistant_pending_tool_calls'::regclass AND relpersistence='u'
|
|
69
|
+
) THEN
|
|
70
|
+
ALTER TABLE assistant_pending_tool_calls SET LOGGED;
|
|
71
|
+
END IF;
|
|
72
|
+
END $$
|
|
73
|
+
`);
|
|
74
|
+
await this.#pool.query(`
|
|
75
|
+
CREATE INDEX IF NOT EXISTS assistant_interactions_session_status_idx
|
|
76
|
+
ON assistant_pending_tool_calls (session_id, status, expires_at)
|
|
77
|
+
`);
|
|
78
|
+
}
|
|
79
|
+
async create(input) {
|
|
80
|
+
const id = `interaction_${randomUUID()}`;
|
|
81
|
+
const interaction = input.kind === 'confirmation'
|
|
82
|
+
? createPendingInteraction(id, input)
|
|
83
|
+
: createPendingInteraction(id, input);
|
|
84
|
+
await this.#transaction(async (client) => {
|
|
85
|
+
// Serialize proposals for one session across service instances so the cap cannot race.
|
|
86
|
+
await client.query(`SELECT pg_advisory_xact_lock(hashtextextended($1, 0))`, [
|
|
87
|
+
interaction.sessionId,
|
|
88
|
+
]);
|
|
89
|
+
await reserveAssistantInteractionPendingSlot(client, interaction.sessionId, interaction.createdAt);
|
|
90
|
+
await insertAssistantPendingInteraction(client, interaction);
|
|
91
|
+
});
|
|
92
|
+
return interaction;
|
|
93
|
+
}
|
|
94
|
+
async claim(input) {
|
|
95
|
+
return this.#claim(input);
|
|
96
|
+
}
|
|
97
|
+
async complete(input) {
|
|
98
|
+
return this.#transaction(async (client) => {
|
|
99
|
+
const current = await this.#lockedInteraction(client, input);
|
|
100
|
+
if (!current)
|
|
101
|
+
return { disposition: 'unavailable' };
|
|
102
|
+
if (isPrunableInteraction(current, input.now)) {
|
|
103
|
+
await client.query(`DELETE FROM assistant_pending_tool_calls WHERE id=$1`, [current.id]);
|
|
104
|
+
return { disposition: 'unavailable' };
|
|
105
|
+
}
|
|
106
|
+
if (isTerminalInteraction(current)) {
|
|
107
|
+
return { disposition: 'replay', interaction: current };
|
|
108
|
+
}
|
|
109
|
+
const mayCompleteExecution = current.status === 'executing' &&
|
|
110
|
+
(input.completion.status === 'succeeded' || input.completion.status === 'failed');
|
|
111
|
+
const mayResolvePending = current.status === 'pending' &&
|
|
112
|
+
(input.completion.status === 'declined' || input.completion.status === 'cancelled');
|
|
113
|
+
if (!mayCompleteExecution && !mayResolvePending) {
|
|
114
|
+
return { disposition: 'conflict', interaction: current };
|
|
115
|
+
}
|
|
116
|
+
const completed = completedInteraction(current, input.completion, input.now);
|
|
117
|
+
const result = await client.query(`UPDATE assistant_pending_tool_calls
|
|
118
|
+
SET status=$2, arguments=NULL, continuation=NULL, review=NULL,
|
|
119
|
+
invocation_context=NULL, completed_at=$3, public_outcome=$4::jsonb,
|
|
120
|
+
payload_scrubbed_at=$3
|
|
121
|
+
WHERE id=$1 RETURNING *`, [
|
|
122
|
+
completed.id,
|
|
123
|
+
completed.status,
|
|
124
|
+
completed.completedAt,
|
|
125
|
+
JSON.stringify(completed.publicOutcome),
|
|
126
|
+
]);
|
|
127
|
+
const row = result.rows[0];
|
|
128
|
+
if (!row)
|
|
129
|
+
throw new Error('assistant interaction disappeared during completion');
|
|
130
|
+
return { disposition: 'completed', interaction: interactionFromRow(row) };
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
async transition(input) {
|
|
134
|
+
return this.#transaction(async (client) => {
|
|
135
|
+
// Use the same session lock as proposal creation so the pending cap remains race-free.
|
|
136
|
+
await client.query(`SELECT pg_advisory_xact_lock(hashtextextended($1, 0))`, [
|
|
137
|
+
input.sessionId,
|
|
138
|
+
]);
|
|
139
|
+
const current = await this.#lockedInteraction(client, input);
|
|
140
|
+
if (!current)
|
|
141
|
+
return { disposition: 'unavailable' };
|
|
142
|
+
if (isPrunableInteraction(current, input.now)) {
|
|
143
|
+
await client.query(`DELETE FROM assistant_pending_tool_calls WHERE id=$1`, [current.id]);
|
|
144
|
+
return { disposition: 'unavailable' };
|
|
145
|
+
}
|
|
146
|
+
if (isTerminalInteraction(current)) {
|
|
147
|
+
return { disposition: 'replay', interaction: current };
|
|
148
|
+
}
|
|
149
|
+
if (current.status !== 'executing') {
|
|
150
|
+
return { disposition: 'conflict', interaction: current };
|
|
151
|
+
}
|
|
152
|
+
const transitioned = transitionedInteractions(current, input, `interaction_${randomUUID()}`);
|
|
153
|
+
await reserveAssistantInteractionPendingSlot(client, current.sessionId, input.now.toISOString());
|
|
154
|
+
await insertAssistantPendingInteraction(client, transitioned.next);
|
|
155
|
+
const result = await client.query(`UPDATE assistant_pending_tool_calls
|
|
156
|
+
SET status='succeeded', arguments=NULL, continuation=NULL, review=NULL,
|
|
157
|
+
invocation_context=NULL, completed_at=$2, public_outcome=$3::jsonb,
|
|
158
|
+
payload_scrubbed_at=$2
|
|
159
|
+
WHERE id=$1 RETURNING *`, [
|
|
160
|
+
transitioned.interaction.id,
|
|
161
|
+
transitioned.interaction.completedAt,
|
|
162
|
+
JSON.stringify(transitioned.interaction.publicOutcome),
|
|
163
|
+
]);
|
|
164
|
+
const row = result.rows[0];
|
|
165
|
+
if (!row)
|
|
166
|
+
throw new Error('assistant interaction disappeared during transition');
|
|
167
|
+
const persisted = interactionFromRow(row);
|
|
168
|
+
if (persisted.status !== 'succeeded') {
|
|
169
|
+
throw new Error('assistant interaction transition did not persist succeeded status');
|
|
170
|
+
}
|
|
171
|
+
return { disposition: 'transitioned', interaction: persisted, next: transitioned.next };
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
async get(input) {
|
|
175
|
+
await this.prune(input.now);
|
|
176
|
+
const result = await this.#pool.query(`SELECT * FROM assistant_pending_tool_calls
|
|
177
|
+
WHERE id=$1 AND session_id=$2 AND deployment_id=$3`, [input.id, input.sessionId, input.deploymentId]);
|
|
178
|
+
return result.rows[0] ? interactionFromRow(result.rows[0]) : undefined;
|
|
179
|
+
}
|
|
180
|
+
async findPending(input) {
|
|
181
|
+
const result = await this.#pool.query(`SELECT * FROM assistant_pending_tool_calls
|
|
182
|
+
WHERE session_id = $1 AND deployment_id = $2 AND status = 'pending' AND expires_at > $3
|
|
183
|
+
ORDER BY created_at DESC LIMIT 1`, [input.sessionId, input.deploymentId, input.now]);
|
|
184
|
+
const row = result.rows[0];
|
|
185
|
+
if (!row)
|
|
186
|
+
return undefined;
|
|
187
|
+
const interaction = interactionFromRow(row);
|
|
188
|
+
return interaction.status === 'pending' ? interaction : undefined;
|
|
189
|
+
}
|
|
190
|
+
async consumeConfirmation(input) {
|
|
191
|
+
const result = await this.#claim(input, 'confirmation');
|
|
192
|
+
return result.disposition === 'claimed' && result.interaction.kind === 'confirmation'
|
|
193
|
+
? result.interaction
|
|
194
|
+
: undefined;
|
|
195
|
+
}
|
|
196
|
+
async prune(now) {
|
|
197
|
+
const completedAt = now.toISOString();
|
|
198
|
+
await this.#pool.query(`UPDATE assistant_pending_tool_calls
|
|
199
|
+
SET status='failed', arguments=NULL, continuation=NULL, review=NULL,
|
|
200
|
+
invocation_context=NULL,
|
|
201
|
+
completed_at=$1, public_outcome=$2::jsonb, payload_scrubbed_at=$1
|
|
202
|
+
WHERE status='executing' AND claimed_at <= $3`, [
|
|
203
|
+
completedAt,
|
|
204
|
+
JSON.stringify(ASSISTANT_INTERACTION_UNKNOWN_OUTCOME),
|
|
205
|
+
new Date(now.getTime() - ASSISTANT_INTERACTION_EXECUTION_LIMIT_MS).toISOString(),
|
|
206
|
+
]);
|
|
207
|
+
await this.#pool.query(`DELETE FROM assistant_pending_tool_calls
|
|
208
|
+
WHERE (status='pending' AND expires_at <= $1)
|
|
209
|
+
OR (status IN ('succeeded','failed','declined','cancelled') AND completed_at <= $2)`, [
|
|
210
|
+
now.toISOString(),
|
|
211
|
+
new Date(now.getTime() - ASSISTANT_INTERACTION_OUTCOME_RETENTION_MS).toISOString(),
|
|
212
|
+
]);
|
|
213
|
+
}
|
|
214
|
+
async #claim(input, expectedKind) {
|
|
215
|
+
return this.#transaction(async (client) => {
|
|
216
|
+
const current = await this.#lockedInteraction(client, input, expectedKind);
|
|
217
|
+
if (!current)
|
|
218
|
+
return { disposition: 'unavailable' };
|
|
219
|
+
if (isPrunableInteraction(current, input.now)) {
|
|
220
|
+
await client.query(`DELETE FROM assistant_pending_tool_calls WHERE id=$1`, [current.id]);
|
|
221
|
+
return { disposition: 'unavailable' };
|
|
222
|
+
}
|
|
223
|
+
if (current.status !== 'pending') {
|
|
224
|
+
return { disposition: 'replay', interaction: current };
|
|
225
|
+
}
|
|
226
|
+
const claimed = executingInteraction(current, input.now);
|
|
227
|
+
const result = await client.query(`UPDATE assistant_pending_tool_calls
|
|
228
|
+
SET status='executing', claimed_at=$2, arguments=NULL, continuation=NULL, review=NULL, invocation_context=NULL, payload_scrubbed_at=$2
|
|
229
|
+
WHERE id=$1 RETURNING *`, [claimed.id, claimed.claimedAt]);
|
|
230
|
+
const row = result.rows[0];
|
|
231
|
+
if (!row)
|
|
232
|
+
throw new Error('assistant interaction disappeared during claim');
|
|
233
|
+
const persisted = interactionFromRow(row);
|
|
234
|
+
if (persisted.status !== 'executing') {
|
|
235
|
+
throw new Error('assistant interaction claim did not persist executing status');
|
|
236
|
+
}
|
|
237
|
+
return { disposition: 'claimed', interaction: claimed };
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
async #lockedInteraction(client, input, expectedKind) {
|
|
241
|
+
const result = await client.query(`SELECT * FROM assistant_pending_tool_calls
|
|
242
|
+
WHERE id=$1 AND session_id=$2 AND deployment_id=$3
|
|
243
|
+
AND ($4::text IS NULL OR kind=$4)
|
|
244
|
+
FOR UPDATE`, [input.id, input.sessionId, input.deploymentId, expectedKind ?? null]);
|
|
245
|
+
const row = result.rows[0];
|
|
246
|
+
if (!row)
|
|
247
|
+
return undefined;
|
|
248
|
+
const interaction = interactionFromRow(row);
|
|
249
|
+
if (!shouldExpireStrandedExecutingInteraction(interaction, input.now))
|
|
250
|
+
return interaction;
|
|
251
|
+
const expired = expireStrandedExecutingInteraction(interaction, input.now);
|
|
252
|
+
const updated = await client.query(`UPDATE assistant_pending_tool_calls
|
|
253
|
+
SET status='failed', arguments=NULL, continuation=NULL, review=NULL,
|
|
254
|
+
invocation_context=NULL,
|
|
255
|
+
completed_at=$2, public_outcome=$3::jsonb, payload_scrubbed_at=$2
|
|
256
|
+
WHERE id=$1 RETURNING *`, [expired.id, expired.completedAt, JSON.stringify(expired.publicOutcome)]);
|
|
257
|
+
const updatedRow = updated.rows[0];
|
|
258
|
+
if (!updatedRow)
|
|
259
|
+
throw new Error('assistant interaction disappeared during expiry');
|
|
260
|
+
return interactionFromRow(updatedRow);
|
|
261
|
+
}
|
|
262
|
+
async #transaction(operation) {
|
|
263
|
+
const client = await this.#pool.connect();
|
|
264
|
+
try {
|
|
265
|
+
await client.query('BEGIN');
|
|
266
|
+
const result = await operation(client);
|
|
267
|
+
await client.query('COMMIT');
|
|
268
|
+
return result;
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
await client.query('ROLLBACK');
|
|
272
|
+
throw error;
|
|
273
|
+
}
|
|
274
|
+
finally {
|
|
275
|
+
client.release();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function interactionFromRow(row) {
|
|
280
|
+
const common = {
|
|
281
|
+
id: row.id,
|
|
282
|
+
sessionId: row.session_id,
|
|
283
|
+
deploymentId: row.deployment_id,
|
|
284
|
+
...(row.invocation_context ? { context: row.invocation_context } : {}),
|
|
285
|
+
...(row.payload_scrubbed_at
|
|
286
|
+
? { payloadScrubbedAt: row.payload_scrubbed_at.toISOString() }
|
|
287
|
+
: {}),
|
|
288
|
+
createdAt: row.created_at.toISOString(),
|
|
289
|
+
expiresAt: row.expires_at.toISOString(),
|
|
290
|
+
};
|
|
291
|
+
const payload = row.kind === 'confirmation'
|
|
292
|
+
? {
|
|
293
|
+
kind: 'confirmation',
|
|
294
|
+
tool: row.tool,
|
|
295
|
+
arguments: row.arguments,
|
|
296
|
+
...(row.continuation !== null ? { continuation: row.continuation } : {}),
|
|
297
|
+
...(row.review !== null ? { review: row.review } : {}),
|
|
298
|
+
}
|
|
299
|
+
: {
|
|
300
|
+
kind: 'input',
|
|
301
|
+
tool: row.tool,
|
|
302
|
+
message: requiredRowValue(row.message, 'message'),
|
|
303
|
+
requestedSchema: requiredRowValue(row.requested_schema, 'requested_schema'),
|
|
304
|
+
continuation: row.continuation,
|
|
305
|
+
};
|
|
306
|
+
if (row.status === 'pending')
|
|
307
|
+
return { ...common, ...payload, status: 'pending' };
|
|
308
|
+
const claimedAt = row.claimed_at?.toISOString();
|
|
309
|
+
if (row.status === 'executing') {
|
|
310
|
+
return {
|
|
311
|
+
...common,
|
|
312
|
+
...payload,
|
|
313
|
+
status: 'executing',
|
|
314
|
+
claimedAt: requiredRowValue(claimedAt, 'claimed_at'),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
const completedAt = requiredRowValue(row.completed_at?.toISOString(), 'completed_at');
|
|
318
|
+
const publicOutcome = normalizePublicOutcome(requiredRowValue(row.public_outcome, 'public_outcome'));
|
|
319
|
+
if (row.status === 'succeeded' || row.status === 'failed') {
|
|
320
|
+
return {
|
|
321
|
+
...common,
|
|
322
|
+
...payload,
|
|
323
|
+
status: row.status,
|
|
324
|
+
claimedAt: requiredRowValue(claimedAt, 'claimed_at'),
|
|
325
|
+
completedAt,
|
|
326
|
+
publicOutcome,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
if (row.status === 'declined' || row.status === 'cancelled') {
|
|
330
|
+
return { ...common, ...payload, status: row.status, completedAt, publicOutcome };
|
|
331
|
+
}
|
|
332
|
+
throw new Error('invalid assistant interaction status in persistence');
|
|
333
|
+
}
|
|
334
|
+
function requiredRowValue(value, field) {
|
|
335
|
+
if (value === null || value === undefined) {
|
|
336
|
+
throw new Error(`assistant interaction row is missing ${field}`);
|
|
337
|
+
}
|
|
338
|
+
return value;
|
|
339
|
+
}
|
|
340
|
+
//# sourceMappingURL=postgres-assistant-interactions.js.map
|