@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,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