@opengeni/db 4.3.3-canary.0 → 4.3.3-canary.2
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/codex-selection-diagnostics.d.ts +12 -0
- package/dist/index.d.ts +23 -1
- package/dist/index.js +260 -49
- package/dist/index.js.map +1 -1
- package/dist/plugin-packages.d.ts +1 -0
- package/dist/sandbox-transition-wait.d.ts +10 -0
- package/dist/session-mcp-progress.d.ts +8 -1
- package/drizzle/0460_host_export_message_attribution.sql +177 -0
- package/package.json +6 -6
- package/src/codex-selection-diagnostics.ts +25 -0
- package/src/computer-sessions.ts +5 -1
- package/src/index.ts +286 -56
- package/src/plugin-packages.ts +3 -0
- package/src/sandbox-transition-wait.ts +45 -0
- package/src/session-mcp-progress.ts +26 -5
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Observational only: expiring a caller's wait never revokes a capture fence. */
|
|
2
|
+
export declare class SandboxTransitionWaitBudget {
|
|
3
|
+
private readonly waitMs;
|
|
4
|
+
private observedCapture;
|
|
5
|
+
private currentDeadline;
|
|
6
|
+
private readonly hardDeadline;
|
|
7
|
+
constructor(waitMs: number, startedAt: number);
|
|
8
|
+
get deadline(): number;
|
|
9
|
+
observeCapture(remainingMs: number | null | undefined, now: number): void;
|
|
10
|
+
}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { type SQL, type SQLWrapper } from "drizzle-orm";
|
|
2
2
|
export declare const SESSION_MCP_PROGRESS_CHARS = 600;
|
|
3
|
+
export declare function sessionTextStoragePrefixChars(maxChars: number): number;
|
|
3
4
|
export declare const SESSION_MCP_PROGRESS_STORAGE_CHARS: number;
|
|
4
5
|
export declare const SESSION_MCP_PROGRESS_UTF16_BASE64_PATTERN = "([A-Za-z0-9+/]{8})*([A-Za-z0-9+/]{8}|[A-Za-z0-9+/]{2}[AEIMQUYcgkosw048]=|[A-Za-z0-9+/]{5}[AQgw]==)";
|
|
5
6
|
/** Validate the full scalar in storage; transfer only this boolean, not its suffix. */
|
|
6
7
|
export declare function sessionMcpProgressScalarIsEncodedSql(value: SQL, codecVersion: SQLWrapper): SQL<boolean>;
|
|
7
|
-
/** Project only the selected scalar, never the rest of
|
|
8
|
+
/** Project only the selected scalar, never the rest of its event payload. */
|
|
9
|
+
export declare function projectSessionTextPrefix(storedPrefix: string | null, storedChars: number | null, codecVersion: number | null, scalarIsEncoded: boolean, maxChars: number): {
|
|
10
|
+
text: string | null;
|
|
11
|
+
originalChars: number | null;
|
|
12
|
+
textTruncated?: true;
|
|
13
|
+
};
|
|
14
|
+
/** Existing MCP progress budget, using the shared logical-text prefix projection. */
|
|
8
15
|
export declare function projectSessionMcpProgressText(storedPrefix: string | null, storedChars: number | null, codecVersion: number | null, scalarIsEncoded: boolean): {
|
|
9
16
|
text: string | null;
|
|
10
17
|
originalChars: number | null;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Attribute accepted user messages through their exact triggering turn. The
|
|
3
|
+
-- source event deliberately has no turn_id; the deferred INSERT trigger runs
|
|
4
|
+
-- after the accepting transaction has installed its turn. Never infer a sender
|
|
5
|
+
-- from the session creator or from arbitrary payload fields. Existing immutable
|
|
6
|
+
-- export rows and consumer checkpoints are unchanged.
|
|
7
|
+
|
|
8
|
+
SET lock_timeout = '5s';
|
|
9
|
+
SET statement_timeout = '10min';
|
|
10
|
+
|
|
11
|
+
DO $migration$
|
|
12
|
+
DECLARE target_schema text := current_schema();
|
|
13
|
+
BEGIN
|
|
14
|
+
EXECUTE format($create$
|
|
15
|
+
CREATE OR REPLACE FUNCTION opengeni_private.enqueue_host_session_event_export()
|
|
16
|
+
RETURNS trigger
|
|
17
|
+
LANGUAGE plpgsql
|
|
18
|
+
SECURITY DEFINER
|
|
19
|
+
SET search_path = pg_catalog
|
|
20
|
+
AS $function$
|
|
21
|
+
DECLARE
|
|
22
|
+
v_enabled boolean;
|
|
23
|
+
v_initiator jsonb;
|
|
24
|
+
v_context jsonb := '{}'::jsonb;
|
|
25
|
+
v_origin text;
|
|
26
|
+
v_attribution_turn_id uuid;
|
|
27
|
+
v_source_payload_bytes integer;
|
|
28
|
+
v_export_payload jsonb;
|
|
29
|
+
v_export_payload_codec_version smallint;
|
|
30
|
+
v_payload_bytes integer;
|
|
31
|
+
BEGIN
|
|
32
|
+
IF NEW.type IN (
|
|
33
|
+
'agent.message.delta', 'agent.reasoning.delta',
|
|
34
|
+
'sandbox.command.output.delta', 'terminal.pty.output.delta'
|
|
35
|
+
) THEN
|
|
36
|
+
RETURN NEW;
|
|
37
|
+
END IF;
|
|
38
|
+
|
|
39
|
+
SELECT c.session_events_enabled INTO v_enabled
|
|
40
|
+
FROM %1$I.host_export_config c WHERE c.id = 1
|
|
41
|
+
FOR SHARE;
|
|
42
|
+
IF coalesce(v_enabled, false) = false THEN
|
|
43
|
+
RETURN NEW;
|
|
44
|
+
END IF;
|
|
45
|
+
|
|
46
|
+
v_attribution_turn_id := NEW.turn_id;
|
|
47
|
+
IF v_attribution_turn_id IS NULL AND NEW.type = 'user.message' THEN
|
|
48
|
+
SELECT min(t.id::text)::uuid INTO v_attribution_turn_id
|
|
49
|
+
FROM %1$I.session_turns t
|
|
50
|
+
WHERE t.account_id = NEW.account_id
|
|
51
|
+
AND t.workspace_id = NEW.workspace_id
|
|
52
|
+
AND t.session_id = NEW.session_id
|
|
53
|
+
AND t.trigger_event_id = NEW.id
|
|
54
|
+
HAVING count(*) = 1;
|
|
55
|
+
END IF;
|
|
56
|
+
|
|
57
|
+
IF v_attribution_turn_id IS NOT NULL THEN
|
|
58
|
+
SELECT
|
|
59
|
+
CASE WHEN octet_length(t.initiator_subject_id) <= 1024 THEN
|
|
60
|
+
jsonb_strip_nulls(jsonb_build_object(
|
|
61
|
+
'kind', t.initiator_kind,
|
|
62
|
+
'subjectId', t.initiator_subject_id,
|
|
63
|
+
'label', CASE
|
|
64
|
+
WHEN jsonb_typeof(t.initiator_context -> 'label') = 'string'
|
|
65
|
+
THEN left(t.initiator_context ->> 'label', 256)
|
|
66
|
+
ELSE NULL
|
|
67
|
+
END
|
|
68
|
+
))
|
|
69
|
+
ELSE NULL END,
|
|
70
|
+
jsonb_strip_nulls(jsonb_build_object(
|
|
71
|
+
'label', CASE
|
|
72
|
+
WHEN jsonb_typeof(t.initiator_context -> 'label') = 'string'
|
|
73
|
+
THEN left(t.initiator_context ->> 'label', 256)
|
|
74
|
+
ELSE NULL
|
|
75
|
+
END,
|
|
76
|
+
'backfill', CASE
|
|
77
|
+
WHEN jsonb_typeof(t.initiator_context -> 'backfill') = 'boolean'
|
|
78
|
+
THEN t.initiator_context -> 'backfill'
|
|
79
|
+
ELSE NULL
|
|
80
|
+
END,
|
|
81
|
+
'attributionOmitted', CASE
|
|
82
|
+
WHEN octet_length(t.initiator_subject_id) > 1024 THEN 'subject_id_too_large'
|
|
83
|
+
ELSE NULL
|
|
84
|
+
END
|
|
85
|
+
)),
|
|
86
|
+
t.source
|
|
87
|
+
INTO v_initiator, v_context, v_origin
|
|
88
|
+
FROM %1$I.session_turns t
|
|
89
|
+
WHERE t.account_id = NEW.account_id
|
|
90
|
+
AND t.workspace_id = NEW.workspace_id
|
|
91
|
+
AND t.session_id = NEW.session_id
|
|
92
|
+
AND t.id = v_attribution_turn_id;
|
|
93
|
+
ELSIF NEW.type = 'session.created' THEN
|
|
94
|
+
SELECT
|
|
95
|
+
CASE WHEN octet_length(s.created_by_subject_id) <= 1024 THEN
|
|
96
|
+
jsonb_strip_nulls(jsonb_build_object(
|
|
97
|
+
'kind', s.created_by_kind,
|
|
98
|
+
'subjectId', s.created_by_subject_id,
|
|
99
|
+
'label', CASE
|
|
100
|
+
WHEN jsonb_typeof(s.created_by_context -> 'label') = 'string'
|
|
101
|
+
THEN left(s.created_by_context ->> 'label', 256)
|
|
102
|
+
ELSE NULL
|
|
103
|
+
END
|
|
104
|
+
))
|
|
105
|
+
ELSE NULL END,
|
|
106
|
+
jsonb_strip_nulls(jsonb_build_object(
|
|
107
|
+
'label', CASE
|
|
108
|
+
WHEN jsonb_typeof(s.created_by_context -> 'label') = 'string'
|
|
109
|
+
THEN left(s.created_by_context ->> 'label', 256)
|
|
110
|
+
ELSE NULL
|
|
111
|
+
END,
|
|
112
|
+
'backfill', CASE
|
|
113
|
+
WHEN jsonb_typeof(s.created_by_context -> 'backfill') = 'boolean'
|
|
114
|
+
THEN s.created_by_context -> 'backfill'
|
|
115
|
+
ELSE NULL
|
|
116
|
+
END,
|
|
117
|
+
'attributionOmitted', CASE
|
|
118
|
+
WHEN octet_length(s.created_by_subject_id) > 1024 THEN 'subject_id_too_large'
|
|
119
|
+
ELSE NULL
|
|
120
|
+
END
|
|
121
|
+
)),
|
|
122
|
+
NULL
|
|
123
|
+
INTO v_initiator, v_context, v_origin
|
|
124
|
+
FROM %1$I.sessions s
|
|
125
|
+
WHERE s.workspace_id = NEW.workspace_id AND s.id = NEW.session_id;
|
|
126
|
+
END IF;
|
|
127
|
+
|
|
128
|
+
v_source_payload_bytes := octet_length(NEW.payload::text);
|
|
129
|
+
v_export_payload := NEW.payload;
|
|
130
|
+
v_export_payload_codec_version := NEW.payload_codec_version;
|
|
131
|
+
IF v_source_payload_bytes > 65536 THEN
|
|
132
|
+
v_export_payload := jsonb_build_object(
|
|
133
|
+
'_hostExport', jsonb_build_object(
|
|
134
|
+
'payloadMode', 'summary',
|
|
135
|
+
'payloadTruncated', true,
|
|
136
|
+
'originalBytes', v_source_payload_bytes,
|
|
137
|
+
'sourceEventId', NEW.id,
|
|
138
|
+
'fullPayload', 'retained in canonical session event'
|
|
139
|
+
),
|
|
140
|
+
'preview', '[host-export payload omitted at bounded projection boundary]'
|
|
141
|
+
);
|
|
142
|
+
-- The projection is literal PostgreSQL JSON, not an application-codec
|
|
143
|
+
-- envelope. Preserve codec truth only when the original payload is kept.
|
|
144
|
+
v_export_payload_codec_version := NULL;
|
|
145
|
+
END IF;
|
|
146
|
+
|
|
147
|
+
v_payload_bytes := octet_length(v_export_payload::text)
|
|
148
|
+
+ octet_length(NEW.type)
|
|
149
|
+
+ coalesce(octet_length(NEW.client_event_id), 0)
|
|
150
|
+
+ coalesce(octet_length(NEW.turn_association), 0)
|
|
151
|
+
+ coalesce(octet_length(NEW.duplicate_reason), 0)
|
|
152
|
+
+ octet_length(coalesce(v_initiator, 'null'::jsonb)::text)
|
|
153
|
+
+ octet_length(v_context::text)
|
|
154
|
+
+ 768;
|
|
155
|
+
INSERT INTO %1$I.host_export_outbox (
|
|
156
|
+
export_kind, source_id, account_id, workspace_id, session_id,
|
|
157
|
+
turn_id, turn_generation, turn_attempt_id, session_sequence,
|
|
158
|
+
client_event_id, turn_association, duplicate_of_event_id, duplicate_reason,
|
|
159
|
+
event_type, idempotency_key, initiator, initiator_context, origin,
|
|
160
|
+
payload, payload_codec_version, envelope_bytes, occurred_at,
|
|
161
|
+
source_recorded_at, enqueued_at
|
|
162
|
+
) VALUES (
|
|
163
|
+
'session_event', NEW.id, NEW.account_id, NEW.workspace_id, NEW.session_id,
|
|
164
|
+
NEW.turn_id, NEW.turn_generation, NEW.turn_attempt_id, NEW.sequence,
|
|
165
|
+
NEW.client_event_id, NEW.turn_association, NEW.duplicate_of_event_id,
|
|
166
|
+
NEW.duplicate_reason,
|
|
167
|
+
NEW.type, 'session_event:' || NEW.id::text, v_initiator,
|
|
168
|
+
coalesce(v_context, '{}'::jsonb), v_origin, v_export_payload,
|
|
169
|
+
v_export_payload_codec_version, greatest(1, v_payload_bytes), NEW.occurred_at,
|
|
170
|
+
NEW.created_at, clock_timestamp()
|
|
171
|
+
)
|
|
172
|
+
ON CONFLICT (export_kind, source_id) DO NOTHING;
|
|
173
|
+
RETURN NEW;
|
|
174
|
+
END $function$;
|
|
175
|
+
$create$, target_schema);
|
|
176
|
+
END
|
|
177
|
+
$migration$;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/db",
|
|
3
|
-
"version": "4.3.3-canary.
|
|
3
|
+
"version": "4.3.3-canary.2",
|
|
4
4
|
"description": "OpenGeni persistence: Drizzle schema, RLS-scoped query layer, the SQL migration runner, and role provisioning.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -98,11 +98,11 @@
|
|
|
98
98
|
"prepublishOnly": "bash ../../scripts/prepublish-guard"
|
|
99
99
|
},
|
|
100
100
|
"dependencies": {
|
|
101
|
-
"@opengeni/codemode": "^0.5.6-canary.
|
|
102
|
-
"@opengeni/codex": "^0.2.22-canary.
|
|
103
|
-
"@opengeni/config": "^1.1.2-canary.
|
|
104
|
-
"@opengeni/contracts": "^3.0.2-canary.
|
|
105
|
-
"@opengeni/network": "^0.3.1-canary.
|
|
101
|
+
"@opengeni/codemode": "^0.5.6-canary.2",
|
|
102
|
+
"@opengeni/codex": "^0.2.22-canary.10",
|
|
103
|
+
"@opengeni/config": "^1.1.2-canary.2",
|
|
104
|
+
"@opengeni/contracts": "^3.0.2-canary.2",
|
|
105
|
+
"@opengeni/network": "^0.3.1-canary.10",
|
|
106
106
|
"drizzle-orm": "^0.45.2",
|
|
107
107
|
"postgres": "^3.4.7"
|
|
108
108
|
},
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Observations about this session, never allocator inputs or failover policy. */
|
|
2
|
+
export function codexSelectionDiagnostics(input: {
|
|
3
|
+
previousCredentialId: string | null;
|
|
4
|
+
credentialId: string;
|
|
5
|
+
reusedLease: boolean;
|
|
6
|
+
pinnedCredentialId: string | null;
|
|
7
|
+
pinSource?: "manual" | "policy" | null;
|
|
8
|
+
}) {
|
|
9
|
+
const transition =
|
|
10
|
+
input.previousCredentialId === input.credentialId
|
|
11
|
+
? ("unchanged" as const)
|
|
12
|
+
: input.previousCredentialId === null
|
|
13
|
+
? ("assigned" as const)
|
|
14
|
+
: ("switched" as const);
|
|
15
|
+
const source =
|
|
16
|
+
input.pinnedCredentialId === input.credentialId && input.pinSource !== "policy"
|
|
17
|
+
? ("manual_pin" as const)
|
|
18
|
+
: ("allocator" as const);
|
|
19
|
+
const reason = input.reusedLease
|
|
20
|
+
? ("lease_reused" as const)
|
|
21
|
+
: transition === "unchanged"
|
|
22
|
+
? ("affinity_reused" as const)
|
|
23
|
+
: transition;
|
|
24
|
+
return { transition, source, reason };
|
|
25
|
+
}
|
package/src/computer-sessions.ts
CHANGED
|
@@ -504,7 +504,11 @@ export async function prepareComputerSessionCreate(
|
|
|
504
504
|
state: "prepared",
|
|
505
505
|
actorSubjectId: input.actorSubjectId,
|
|
506
506
|
})
|
|
507
|
-
|
|
507
|
+
// The operation also has a workspace/operation unique index. Concurrent
|
|
508
|
+
// speculative inserts can collide there even when the primary-key
|
|
509
|
+
// arbiter would deduplicate them. Arbitrate every constraint, then use
|
|
510
|
+
// the scoped lookup and immutable request checks below to authorize replay.
|
|
511
|
+
.onConflictDoNothing()
|
|
508
512
|
.returning();
|
|
509
513
|
if (!insertedOperation) {
|
|
510
514
|
const existing = await loadOperation(tx, input.workspaceId, input.operationId);
|