@kindgi/agents 0.1.2 → 0.1.3
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/conversation-binding.d.ts +9 -1
- package/dist/conversation-binding.d.ts.map +1 -1
- package/dist/define.js +20 -0
- package/dist/define.js.map +1 -1
- package/dist/guardrails-gate.d.ts +7 -4
- package/dist/guardrails-gate.d.ts.map +1 -1
- package/dist/guardrails-gate.js +5 -2
- package/dist/guardrails-gate.js.map +1 -1
- package/dist/handlers/context.d.ts +14 -0
- package/dist/handlers/context.d.ts.map +1 -1
- package/dist/handlers/dispatch-tools.d.ts.map +1 -1
- package/dist/handlers/dispatch-tools.js +18 -42
- package/dist/handlers/dispatch-tools.js.map +1 -1
- package/dist/handlers/evaluate-guardrails.d.ts.map +1 -1
- package/dist/handlers/evaluate-guardrails.js +34 -1
- package/dist/handlers/evaluate-guardrails.js.map +1 -1
- package/dist/handlers/gate-decision.d.ts +62 -0
- package/dist/handlers/gate-decision.d.ts.map +1 -0
- package/dist/handlers/gate-decision.js +55 -0
- package/dist/handlers/gate-decision.js.map +1 -0
- package/dist/handlers/model-call.d.ts +10 -0
- package/dist/handlers/model-call.d.ts.map +1 -1
- package/dist/handlers/model-call.js +109 -34
- package/dist/handlers/model-call.js.map +1 -1
- package/dist/handlers/persist-provenance.d.ts.map +1 -1
- package/dist/handlers/persist-provenance.js +3 -1
- package/dist/handlers/persist-provenance.js.map +1 -1
- package/dist/handlers/persist-user-message.d.ts.map +1 -1
- package/dist/handlers/persist-user-message.js +5 -16
- package/dist/handlers/persist-user-message.js.map +1 -1
- package/dist/handlers/public-types.d.ts +16 -2
- package/dist/handlers/public-types.d.ts.map +1 -1
- package/dist/handlers/rehydrate.d.ts.map +1 -1
- package/dist/handlers/rehydrate.js +85 -0
- package/dist/handlers/rehydrate.js.map +1 -1
- package/dist/handlers/run-retrievals.d.ts.map +1 -1
- package/dist/handlers/run-retrievals.js +2 -19
- package/dist/handlers/run-retrievals.js.map +1 -1
- package/dist/handlers/setup.d.ts.map +1 -1
- package/dist/handlers/setup.js +15 -10
- package/dist/handlers/setup.js.map +1 -1
- package/dist/handlers/tool-hitl.d.ts +5 -9
- package/dist/handlers/tool-hitl.d.ts.map +1 -1
- package/dist/handlers/tool-hitl.js +5 -0
- package/dist/handlers/tool-hitl.js.map +1 -1
- package/dist/handlers/turn-provenance.d.ts +67 -0
- package/dist/handlers/turn-provenance.d.ts.map +1 -0
- package/dist/handlers/turn-provenance.js +160 -0
- package/dist/handlers/turn-provenance.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/invoke.d.ts +14 -1
- package/dist/invoke.d.ts.map +1 -1
- package/dist/invoke.js +34 -19
- package/dist/invoke.js.map +1 -1
- package/dist/provenance-emit.d.ts +4 -2
- package/dist/provenance-emit.d.ts.map +1 -1
- package/dist/provenance-emit.js +4 -2
- package/dist/provenance-emit.js.map +1 -1
- package/dist/schema.d.ts +17 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +11 -0
- package/dist/schema.js.map +1 -1
- package/dist/types.d.ts +15 -1
- package/dist/types.d.ts.map +1 -1
- package/migrations/0003_hesitant_captain_cross.sql +2 -0
- package/migrations/meta/0003_snapshot.json +321 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +15 -15
- package/src/conversation-binding.ts +9 -1
- package/src/define.ts +20 -0
- package/src/guardrails-gate.ts +17 -3
- package/src/handlers/context.ts +14 -0
- package/src/handlers/dispatch-tools.ts +21 -43
- package/src/handlers/evaluate-guardrails.ts +39 -1
- package/src/handlers/gate-decision.ts +101 -0
- package/src/handlers/model-call.ts +149 -33
- package/src/handlers/persist-provenance.ts +3 -1
- package/src/handlers/persist-user-message.ts +3 -16
- package/src/handlers/public-types.ts +16 -2
- package/src/handlers/rehydrate.ts +120 -3
- package/src/handlers/run-retrievals.ts +2 -19
- package/src/handlers/setup.ts +17 -20
- package/src/handlers/tool-hitl.ts +6 -10
- package/src/handlers/turn-provenance.ts +230 -0
- package/src/index.ts +7 -0
- package/src/invoke.ts +46 -20
- package/src/provenance-emit.ts +4 -1
- package/src/schema.ts +11 -0
- package/src/types.ts +15 -1
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The provenance nodes and edges an agent turn records, in one place:
|
|
6
|
+
* the steps add them as they run, and a turn resumed after a park
|
|
7
|
+
* (`rehydrate.ts`) adds those of the steps that ran before it from what
|
|
8
|
+
* they left durable (the conversation, the journal). Either way the DAG
|
|
9
|
+
* is the same.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ProvenanceBuilder } from '@kindgi/provenance';
|
|
13
|
+
import type { Timestamp } from '@kindgi/types';
|
|
14
|
+
|
|
15
|
+
import type { ConversationMessage, RetrievedFact } from '../types.js';
|
|
16
|
+
import type { TurnContext } from './context.js';
|
|
17
|
+
import type { GateDecision } from './gate-decision.js';
|
|
18
|
+
|
|
19
|
+
/** The turn's user message. */
|
|
20
|
+
export function addInputNode(provenance: ProvenanceBuilder, message: ConversationMessage): void {
|
|
21
|
+
provenance.addNode({
|
|
22
|
+
id: `input:${message.sequence}`,
|
|
23
|
+
kind: 'input',
|
|
24
|
+
timestamp: message.createdAt,
|
|
25
|
+
...(message.actor !== undefined && { actor: message.actor }),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The facts the turn retrieved for its user message. */
|
|
30
|
+
export function addRetrievalNodes(
|
|
31
|
+
provenance: ProvenanceBuilder,
|
|
32
|
+
retrieved: readonly RetrievedFact[],
|
|
33
|
+
input: ConversationMessage,
|
|
34
|
+
): void {
|
|
35
|
+
for (const r of retrieved) {
|
|
36
|
+
provenance.addNode({
|
|
37
|
+
id: `retrieval:${r.fact.id}`,
|
|
38
|
+
kind: 'retrieval',
|
|
39
|
+
timestamp: input.createdAt,
|
|
40
|
+
...(r.fact.contentHash !== undefined && { contentHash: r.fact.contentHash }),
|
|
41
|
+
attributes: {
|
|
42
|
+
factId: r.fact.id,
|
|
43
|
+
factType: r.fact.type,
|
|
44
|
+
intentScope: r.intent.scope,
|
|
45
|
+
...(r.score !== undefined && { score: r.score }),
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
provenance.addEdge({
|
|
49
|
+
from: `retrieval:${r.fact.id}`,
|
|
50
|
+
to: `input:${input.sequence}`,
|
|
51
|
+
kind: 'influenced-by',
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** One model call: its identity; its usage is the cost ledger's, by `callId`. */
|
|
57
|
+
export interface ModelCallFacts {
|
|
58
|
+
readonly step: number;
|
|
59
|
+
readonly callId: string;
|
|
60
|
+
readonly provider: { readonly id: string; readonly model: string };
|
|
61
|
+
readonly finishReason: string;
|
|
62
|
+
readonly at: Timestamp;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A model call, `caused-by` the turn's user message and `influenced-by`
|
|
67
|
+
* each of this turn's tool results it read: all of them so far, since
|
|
68
|
+
* the call's input holds every message of the turn before it.
|
|
69
|
+
*/
|
|
70
|
+
export function addModelCallNode(
|
|
71
|
+
provenance: ProvenanceBuilder,
|
|
72
|
+
call: ModelCallFacts,
|
|
73
|
+
inputSequence: number,
|
|
74
|
+
toolResultIds: readonly string[],
|
|
75
|
+
): void {
|
|
76
|
+
const id = `model-call:${call.step}`;
|
|
77
|
+
provenance.addNode({
|
|
78
|
+
id,
|
|
79
|
+
kind: 'model-call',
|
|
80
|
+
timestamp: call.at,
|
|
81
|
+
modelVersion: `${call.provider.id}/${call.provider.model}`,
|
|
82
|
+
attributes: {
|
|
83
|
+
step: call.step,
|
|
84
|
+
callId: call.callId,
|
|
85
|
+
providerId: call.provider.id,
|
|
86
|
+
model: call.provider.model,
|
|
87
|
+
finishReason: call.finishReason,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
provenance.addEdge({ from: id, to: `input:${inputSequence}`, kind: 'caused-by' });
|
|
91
|
+
for (const invocationId of toolResultIds) {
|
|
92
|
+
provenance.addEdge({ from: id, to: `tool-result:${invocationId}`, kind: 'influenced-by' });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* A tool call the model at `step` made, and its result: the stored tool
|
|
98
|
+
* message (a tool's output, a rejected approval's, or a failed call's).
|
|
99
|
+
* `version` is the tool the agent's binding resolved, when it has one.
|
|
100
|
+
*/
|
|
101
|
+
export function addToolNodes(
|
|
102
|
+
provenance: ProvenanceBuilder,
|
|
103
|
+
step: number,
|
|
104
|
+
result: ConversationMessage,
|
|
105
|
+
version: { readonly resolvedVersion: string; readonly requestedRange: string } | undefined,
|
|
106
|
+
): void {
|
|
107
|
+
const call = result.toolCall;
|
|
108
|
+
if (call === undefined) return;
|
|
109
|
+
const callNodeId = `tool-call:${call.invocationId}`;
|
|
110
|
+
const resultNodeId = `tool-result:${call.invocationId}`;
|
|
111
|
+
provenance.addNode({
|
|
112
|
+
id: callNodeId,
|
|
113
|
+
kind: 'tool-call',
|
|
114
|
+
timestamp: result.createdAt,
|
|
115
|
+
attributes: {
|
|
116
|
+
toolId: call.toolId,
|
|
117
|
+
invocationId: call.invocationId,
|
|
118
|
+
// The version the registry picked for the turn and the range the
|
|
119
|
+
// agent asked for, so a replay can pin against the same version.
|
|
120
|
+
...(version !== undefined && {
|
|
121
|
+
toolVersion: version.resolvedVersion,
|
|
122
|
+
toolVersionRange: version.requestedRange,
|
|
123
|
+
}),
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
provenance.addNode({ id: resultNodeId, kind: 'tool-result', timestamp: result.createdAt });
|
|
127
|
+
provenance.addEdge({ from: callNodeId, to: `model-call:${step}`, kind: 'invoked' });
|
|
128
|
+
provenance.addEdge({ from: resultNodeId, to: callNodeId, kind: 'produced' });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The tool calls of one model step: a node pair for each result the step
|
|
133
|
+
* stored, and their invocation ids added to the turn's tool results.
|
|
134
|
+
*/
|
|
135
|
+
export function addStepToolNodes(
|
|
136
|
+
ctx: TurnContext,
|
|
137
|
+
step: number,
|
|
138
|
+
stored: readonly ConversationMessage[],
|
|
139
|
+
): void {
|
|
140
|
+
for (const result of stored) {
|
|
141
|
+
if (result.role !== 'tool' || result.toolCall === undefined) continue;
|
|
142
|
+
const { invocationId, toolId } = result.toolCall;
|
|
143
|
+
if (ctx.provenance !== undefined) {
|
|
144
|
+
addToolNodes(ctx.provenance, step, result, versionOf(ctx, toolId));
|
|
145
|
+
const approval = ctx.toolApprovals?.get(invocationId);
|
|
146
|
+
if (approval !== undefined) {
|
|
147
|
+
addToolApprovalNodes(
|
|
148
|
+
ctx.provenance,
|
|
149
|
+
invocationId,
|
|
150
|
+
approval,
|
|
151
|
+
ctx.input.agent.id as unknown as string,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
ctx.toolResultIds ??= [];
|
|
156
|
+
ctx.toolResultIds.push(invocationId);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** A tool call's approval, decided: the wait it parked on and the answer. */
|
|
161
|
+
export interface ToolApproval {
|
|
162
|
+
readonly waitTokenId: string;
|
|
163
|
+
/** When the call parked on the approval. */
|
|
164
|
+
readonly parkedAt: Timestamp;
|
|
165
|
+
/** When the decision reached the run. */
|
|
166
|
+
readonly decidedAt: Timestamp;
|
|
167
|
+
readonly decision: GateDecision;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* The approval a tool call waited on, as the session gate records its own:
|
|
172
|
+
* a `wait` node (the agent parked) `resumed-from` a `resume` node (the
|
|
173
|
+
* decision, its actor whoever decided). The call `waited-on` the wait, and
|
|
174
|
+
* its result was `caused-by` the decision: the tool's output when approved,
|
|
175
|
+
* the rejection when not.
|
|
176
|
+
*/
|
|
177
|
+
export function addToolApprovalNodes(
|
|
178
|
+
provenance: ProvenanceBuilder,
|
|
179
|
+
invocationId: string,
|
|
180
|
+
approval: ToolApproval,
|
|
181
|
+
agentId: string,
|
|
182
|
+
): void {
|
|
183
|
+
const waitId = `tool-hitl-gate-wait:${invocationId}`;
|
|
184
|
+
const resumeId = `tool-hitl-gate-resume:${invocationId}`;
|
|
185
|
+
const { decision } = approval;
|
|
186
|
+
provenance.addNode({
|
|
187
|
+
id: waitId,
|
|
188
|
+
kind: 'wait',
|
|
189
|
+
timestamp: approval.parkedAt,
|
|
190
|
+
actor: `agent:${agentId}`,
|
|
191
|
+
attributes: { gate: 'tool-call', invocationId, waitTokenId: approval.waitTokenId },
|
|
192
|
+
});
|
|
193
|
+
provenance.addNode({
|
|
194
|
+
id: resumeId,
|
|
195
|
+
kind: 'resume',
|
|
196
|
+
timestamp: approval.decidedAt,
|
|
197
|
+
...(decision.decidedBy !== undefined && { actor: decision.decidedBy }),
|
|
198
|
+
attributes: {
|
|
199
|
+
gate: 'tool-call',
|
|
200
|
+
decision: decisionOf(decision),
|
|
201
|
+
...(!decision.approved &&
|
|
202
|
+
decision.reason === 'rejected' &&
|
|
203
|
+
decision.rationale !== undefined && { rationale: decision.rationale }),
|
|
204
|
+
...(decision.approvalId !== undefined && { approvalId: decision.approvalId }),
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
provenance.addEdge({ from: `tool-call:${invocationId}`, to: waitId, kind: 'waited-on' });
|
|
208
|
+
provenance.addEdge({ from: waitId, to: resumeId, kind: 'resumed-from' });
|
|
209
|
+
provenance.addEdge({ from: `tool-result:${invocationId}`, to: resumeId, kind: 'caused-by' });
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** A gate decision as a `resume` node records it. */
|
|
213
|
+
export function decisionOf(decision: GateDecision): 'approve' | 'reject' | 'unreadable' {
|
|
214
|
+
if (decision.approved) return 'approve';
|
|
215
|
+
return decision.reason === 'rejected' ? 'reject' : 'unreadable';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** The version the agent's binding of `toolId` resolved, if it has one. */
|
|
219
|
+
function versionOf(
|
|
220
|
+
ctx: TurnContext,
|
|
221
|
+
toolId: string,
|
|
222
|
+
): { readonly resolvedVersion: string; readonly requestedRange: string } | undefined {
|
|
223
|
+
const bindings = [...(ctx.tools?.byName.values() ?? [])];
|
|
224
|
+
const binding =
|
|
225
|
+
ctx.tools?.byName.get(toolId) ??
|
|
226
|
+
bindings.find((b) => (b.tool.id as unknown as string) === toolId);
|
|
227
|
+
return binding === undefined
|
|
228
|
+
? undefined
|
|
229
|
+
: { resolvedVersion: binding.resolvedVersion, requestedRange: binding.requestedRange };
|
|
230
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -19,6 +19,13 @@ export type {
|
|
|
19
19
|
export { defineAgent } from './define.js';
|
|
20
20
|
export type { DefineAgentSpec } from './define.js';
|
|
21
21
|
export { resolveEffectiveHitlPolicy } from './hitl-policy.js';
|
|
22
|
+
export {
|
|
23
|
+
AGENT_GATE_SUBJECTS,
|
|
24
|
+
SESSION_GATE_SUBJECT,
|
|
25
|
+
TOOL_CALL_GATE_SUBJECT,
|
|
26
|
+
readGateDecision,
|
|
27
|
+
} from './handlers/gate-decision.js';
|
|
28
|
+
export type { GateDecision, GateDecisionValue } from './handlers/gate-decision.js';
|
|
22
29
|
export type { EffectiveHitlPolicy } from './hitl-policy.js';
|
|
23
30
|
export { agentStepOutput, invokeAgent, resumeAgentTurn } from './invoke.js';
|
|
24
31
|
export type {
|
package/src/invoke.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import type { Principal } from '@kindgi/authz';
|
|
5
5
|
import type { KernelError, RunResult } from '@kindgi/runtime';
|
|
6
|
-
import type { Result, RunId, TenantId } from '@kindgi/types';
|
|
6
|
+
import type { OrgId, Result, RunId, TenantId } from '@kindgi/types';
|
|
7
7
|
|
|
8
8
|
import { AGENT_TURN_FLOW } from './agent-turn-flow.js';
|
|
9
9
|
import { DEFAULT_MAX_WALL_MS } from './handlers/constants.js';
|
|
@@ -14,6 +14,7 @@ import type { InvokeAgentBindings, InvokeAgentInput } from './handlers/public-ty
|
|
|
14
14
|
import { rehydrateTurnContext } from './handlers/rehydrate.js';
|
|
15
15
|
import type { AgentTurnResult } from './handlers/result-shape.js';
|
|
16
16
|
import { projectRunResult } from './project-run-result.js';
|
|
17
|
+
import type { RunSnapshotRecord } from './run-snapshot-binding.js';
|
|
17
18
|
import type { Agent } from './types.js';
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -82,6 +83,12 @@ export async function invokeAgent(
|
|
|
82
83
|
flow: AGENT_TURN_FLOW,
|
|
83
84
|
handlers,
|
|
84
85
|
input: input.userMessage,
|
|
86
|
+
// The run's record names the agent, its version and the conversation.
|
|
87
|
+
agent: {
|
|
88
|
+
id: input.agent.id,
|
|
89
|
+
version: input.agent.version,
|
|
90
|
+
conversationId: input.conversationId,
|
|
91
|
+
},
|
|
85
92
|
...(input.parent !== undefined && { parent: input.parent }),
|
|
86
93
|
...(input.dryRun === true && { options: { dryRun: true } }),
|
|
87
94
|
// Authorization — carry principal + authz into the run so every
|
|
@@ -106,6 +113,12 @@ export interface ResumeAgentTurnInput {
|
|
|
106
113
|
* handler's agent-version-mismatch guard).
|
|
107
114
|
*/
|
|
108
115
|
readonly agent: Agent;
|
|
116
|
+
/**
|
|
117
|
+
* The org of the run's project (the snapshot's `projectId`), when it has
|
|
118
|
+
* one. The caller resolves it from the project, as for `invokeAgent`'s
|
|
119
|
+
* `orgId`, so the resumed turn's tools and guardrails see it too.
|
|
120
|
+
*/
|
|
121
|
+
readonly orgId?: OrgId;
|
|
109
122
|
}
|
|
110
123
|
|
|
111
124
|
/**
|
|
@@ -203,25 +216,7 @@ export async function resumeAgentTurn(
|
|
|
203
216
|
}
|
|
204
217
|
|
|
205
218
|
// 3. Reconstruct the InvokeAgentInput envelope from the snapshot.
|
|
206
|
-
const reconstructedInput
|
|
207
|
-
tenantId: snapshot.tenantId,
|
|
208
|
-
projectId: snapshot.projectId,
|
|
209
|
-
agent: input.agent,
|
|
210
|
-
conversationId: snapshot.conversationId,
|
|
211
|
-
userMessage: snapshot.userMessage,
|
|
212
|
-
...(snapshot.parameters !== undefined && { parameters: snapshot.parameters }),
|
|
213
|
-
...(snapshot.input !== undefined && { input: snapshot.input }),
|
|
214
|
-
...(snapshot.participantId !== undefined && { participantId: snapshot.participantId }),
|
|
215
|
-
...(snapshot.dryRun && { dryRun: true }),
|
|
216
|
-
...(snapshot.principal !== undefined &&
|
|
217
|
-
snapshot.principal !== null && {
|
|
218
|
-
principal: snapshot.principal as Principal,
|
|
219
|
-
}),
|
|
220
|
-
...(snapshot.authz !== undefined &&
|
|
221
|
-
snapshot.authz !== null && {
|
|
222
|
-
authz: snapshot.authz as { readonly fgaApiUrl: string },
|
|
223
|
-
}),
|
|
224
|
-
};
|
|
219
|
+
const reconstructedInput = turnInputFromSnapshot(snapshot, input);
|
|
225
220
|
|
|
226
221
|
// 4. Build the same TurnContext + handlers as invokeAgent().
|
|
227
222
|
const started = Date.now();
|
|
@@ -299,3 +294,34 @@ export type {
|
|
|
299
294
|
InvokeAgentBindings,
|
|
300
295
|
InvokeAgentInput,
|
|
301
296
|
} from './handlers/public-types.js';
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* The `InvokeAgentInput` a resumed turn runs with: the one captured in its
|
|
300
|
+
* snapshot at run start, the agent the caller resolved, and the org of the
|
|
301
|
+
* run's project, which the caller resolves as for `invokeAgent`.
|
|
302
|
+
*/
|
|
303
|
+
export function turnInputFromSnapshot(
|
|
304
|
+
snapshot: RunSnapshotRecord,
|
|
305
|
+
input: Pick<ResumeAgentTurnInput, 'agent' | 'orgId'>,
|
|
306
|
+
): InvokeAgentInput {
|
|
307
|
+
return {
|
|
308
|
+
tenantId: snapshot.tenantId,
|
|
309
|
+
projectId: snapshot.projectId,
|
|
310
|
+
...(input.orgId !== undefined && { orgId: input.orgId }),
|
|
311
|
+
agent: input.agent,
|
|
312
|
+
conversationId: snapshot.conversationId,
|
|
313
|
+
userMessage: snapshot.userMessage,
|
|
314
|
+
...(snapshot.parameters !== undefined && { parameters: snapshot.parameters }),
|
|
315
|
+
...(snapshot.input !== undefined && { input: snapshot.input }),
|
|
316
|
+
...(snapshot.participantId !== undefined && { participantId: snapshot.participantId }),
|
|
317
|
+
...(snapshot.dryRun && { dryRun: true }),
|
|
318
|
+
...(snapshot.principal !== undefined &&
|
|
319
|
+
snapshot.principal !== null && {
|
|
320
|
+
principal: snapshot.principal as Principal,
|
|
321
|
+
}),
|
|
322
|
+
...(snapshot.authz !== undefined &&
|
|
323
|
+
snapshot.authz !== null && {
|
|
324
|
+
authz: snapshot.authz as { readonly fgaApiUrl: string },
|
|
325
|
+
}),
|
|
326
|
+
};
|
|
327
|
+
}
|
package/src/provenance-emit.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
Provenance,
|
|
7
7
|
ProvenanceBuilder,
|
|
8
8
|
ProvenanceEmitBinding,
|
|
9
|
+
ProvenanceEmitContext,
|
|
9
10
|
} from '@kindgi/provenance';
|
|
10
11
|
import { signProvenance } from '@kindgi/provenance';
|
|
11
12
|
import type { Result } from '@kindgi/types';
|
|
@@ -55,6 +56,8 @@ export interface ProvenanceBindings {
|
|
|
55
56
|
export async function persistProvenance(
|
|
56
57
|
builder: ProvenanceBuilder,
|
|
57
58
|
bindings: ProvenanceBindings,
|
|
59
|
+
/** Stored beside the record (the turn's project); see `ProvenanceEmitContext`. */
|
|
60
|
+
context?: ProvenanceEmitContext,
|
|
58
61
|
): Promise<Result<Provenance, PersistenceError>> {
|
|
59
62
|
let provenance: Provenance = builder.snapshot();
|
|
60
63
|
if (bindings.keyProvider !== undefined) {
|
|
@@ -84,7 +87,7 @@ export async function persistProvenance(
|
|
|
84
87
|
},
|
|
85
88
|
};
|
|
86
89
|
}
|
|
87
|
-
const emitted = await bindings.emitBinding.emit(provenance);
|
|
90
|
+
const emitted = await bindings.emitBinding.emit(provenance, context);
|
|
88
91
|
if (emitted.kind === 'err') {
|
|
89
92
|
return {
|
|
90
93
|
kind: 'err',
|
package/src/schema.ts
CHANGED
|
@@ -43,6 +43,13 @@ export const agentConversations = pgTable(
|
|
|
43
43
|
* SSO subject). Kept as text so the caller decides the shape.
|
|
44
44
|
*/
|
|
45
45
|
participantId: text('participant_id'),
|
|
46
|
+
/**
|
|
47
|
+
* The project the conversation is in, set when it is opened (a run
|
|
48
|
+
* opens it in the run's project). Null when opened without one, and
|
|
49
|
+
* on conversations from before the column (nothing is backfilled).
|
|
50
|
+
* No foreign key: `projects` belongs to the runtime's schema.
|
|
51
|
+
*/
|
|
52
|
+
projectId: uuid('project_id'),
|
|
46
53
|
/**
|
|
47
54
|
* Structural scope — project id, matter id, etc. Persisted through
|
|
48
55
|
* the versioning envelope so shape evolution is migrate-on-read.
|
|
@@ -74,6 +81,10 @@ export const agentConversations = pgTable(
|
|
|
74
81
|
t.tenantId,
|
|
75
82
|
t.participantId,
|
|
76
83
|
),
|
|
84
|
+
/** One project's conversations, newest first (`listConversationsPage({ scope })`). */
|
|
85
|
+
tenantProjectOpenedIdx: index('agent_conversations_tenant_project_opened_idx')
|
|
86
|
+
.on(t.tenantId, t.projectId, t.openedAt.desc())
|
|
87
|
+
.where(sql`${t.projectId} IS NOT NULL`),
|
|
77
88
|
/** Partial index: open conversations sorted by recency. Used by the UI list. */
|
|
78
89
|
openRecentIdx: index('agent_conversations_open_recent_idx')
|
|
79
90
|
.on(t.tenantId, t.lastMessageAt.desc())
|
package/src/types.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import type { Capability } from '@kindgi/capabilities';
|
|
5
5
|
import type { Fact, MemoryScope } from '@kindgi/memory';
|
|
6
6
|
import type { ToolErrorsSpec, ToolHitlMode, ToolHitlRule } from '@kindgi/policy-contract';
|
|
7
|
-
import type { Brand, ConversationId, Semver, TenantId, Timestamp } from '@kindgi/types';
|
|
7
|
+
import type { Brand, ConversationId, ProjectId, Semver, TenantId, Timestamp } from '@kindgi/types';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Branded agent id. Convention: dotted namespace under the tenant's
|
|
@@ -204,6 +204,14 @@ export interface ConversationHitlPolicy {
|
|
|
204
204
|
* (see `resolveEffectiveHitlPolicy`).
|
|
205
205
|
*/
|
|
206
206
|
readonly timeoutMs?: number;
|
|
207
|
+
/**
|
|
208
|
+
* What happens when an approval's time runs out: it escalates one
|
|
209
|
+
* reviewer tier, and at `admin` it expires (the turn fails with
|
|
210
|
+
* `hitl-cancelled`). `'escalate'` is the only behavior today, and the
|
|
211
|
+
* default; `'auto-approve'` and `'auto-reject'` are refused rather than
|
|
212
|
+
* accepted and ignored.
|
|
213
|
+
*/
|
|
214
|
+
readonly onTimeout?: 'escalate';
|
|
207
215
|
}
|
|
208
216
|
|
|
209
217
|
/**
|
|
@@ -416,6 +424,12 @@ export interface Conversation {
|
|
|
416
424
|
readonly title: string;
|
|
417
425
|
/** UUID or free-form identifier for the human participant. */
|
|
418
426
|
readonly participantId?: string;
|
|
427
|
+
/**
|
|
428
|
+
* The project the conversation is in, set when it is opened (by a run,
|
|
429
|
+
* from the run's project). Absent on conversations opened without one,
|
|
430
|
+
* and on those from before it was stored.
|
|
431
|
+
*/
|
|
432
|
+
readonly projectId?: ProjectId;
|
|
419
433
|
/** Additional attributes (project id, matter id, etc.). */
|
|
420
434
|
readonly scope: MemoryScope;
|
|
421
435
|
readonly openedAt: Timestamp;
|