Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/20260918094326-regenerate-sdk-from-openapi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@truefoundry/trueforge-sdk": patch
---

Regenerate SDK from updated OpenAPI spec.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SDK changeset without SDK regen

Low Severity

This changeset bumps @truefoundry/trueforge-sdk for an OpenAPI regen that is not in the PR. The new send-event schema is not wired to a route, and neither openapi.json nor the SDK were regenerated, so merge would publish an empty SDK patch.

Fix in Cursor Fix in Web

Triggered by project rule: TrueForge review rules

Reviewed by Cursor Bugbot for commit b981228. Configure here.

6 changes: 6 additions & 0 deletions .changeset/user-tool-approval-policy-event.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@truefoundry/trueforge-core": patch
"@truefoundry/trueforge": patch
---

Add `user.tool_approval_policy` send-event schema (`allow_session`, optional ISO `expire_at`). Send-only like `user.tool_approval` / `user.tool_response` — not on the durable stream.
25 changes: 17 additions & 8 deletions packages/trueforge-core/src/agent-session/schemas/sendEvent.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
/**
* Inbound send-event payloads for tip HITL (client → harness), distinct from the
* stream log ({@link PersistedTurnEvent} / session_event).
* Inbound send-event payloads (client → harness), distinct from the stream log
* ({@link PersistedTurnEvent} / session_event).
*
* Public send is session-scoped (`POST …/sessions/{id}/events`) with required
* body `turn_id` (one batch → one tip) plus `SessionInboundEventItem`s; rows stamp that
* tip id. v1 union is tip-only; approval policies may relax `turn_id` later.
* Public send is session-scoped (`POST …/sessions/{id}/events`) with body
* `turn_id` + items. Tip HITL uses approval/tool_response; sticky policies use
* `user.tool_approval_policy` (session `allow_session`, optional expiry; may later
* relax tip binding). Per-call allow/deny stays on `user.tool_approval`.
* `user.message` stays on createTurn / steer.
*/
import { z } from '@hono/zod-openapi';
import { UserToolApprovalMessageSchema, UserToolResponseMessageSchema } from '../../core/events/schema';
import {
UserToolApprovalMessageSchema,
UserToolApprovalPolicyMessageSchema,
UserToolResponseMessageSchema,
} from '../../core/events/schema';

export const SessionInboundEventItemSchema = z
.discriminatedUnion('type', [UserToolApprovalMessageSchema, UserToolResponseMessageSchema])
.openapi('SessionInboundEventItem');
.discriminatedUnion('type', [
UserToolApprovalMessageSchema,
UserToolResponseMessageSchema,
UserToolApprovalPolicyMessageSchema,
])
.openapi('SendTurnEventItem');

export type SessionInboundEventItem = z.infer<typeof SessionInboundEventItemSchema>;
34 changes: 34 additions & 0 deletions packages/trueforge-core/src/core/events/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const EventType = {
TOOL_RESPONSE_REQUIRED: 'tool.response_required',
USER_TOOL_APPROVAL: 'user.tool_approval',
USER_TOOL_RESPONSE: 'user.tool_response',
USER_TOOL_APPROVAL_POLICY: 'user.tool_approval_policy',
USER_MESSAGE: 'user.message',
} as const;

Expand Down Expand Up @@ -96,6 +97,37 @@ export const UserToolResponseMessageSchema = z
})
.openapi('UserToolResponseEvent');

export const ToolApprovalPolicyAllowSessionSchema = z
.object({
type: z.literal('allow_session').describe('Allow matching tool calls for the rest of this session.'),
expire_at: z
.string()
.optional()
.describe('ISO 8601 timestamp when this session allow expires. Omit to allow for the whole session.'),
})
.openapi('ToolApprovalPolicyAllowSession');

export const ToolApprovalPolicyItemSchema = z
.object({
server: z.string().min(1, 'server is required').describe('MCP server name the tool belongs to.'),
tool_name: z.string().min(1, 'tool_name is required').describe('Tool name this policy applies to.'),
action: z.discriminatedUnion('type', [ToolApprovalPolicyAllowSessionSchema]),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Single-variant action union

Low Severity

The action field is a one-member discriminatedUnion on type. That emits a single-variant oneOf instead of a flat object alias, which new wire schemas avoid until a second action variant exists.

Fix in Cursor Fix in Web

Triggered by project rule: @truefoundry/trueforge review rules

Reviewed by Cursor Bugbot for commit f2e3aa5. Configure here.

})
.openapi('ToolApprovalPolicyItem');

/**
* Client inbound sticky approval policy (send-event only; not on the durable stream).
* Same pattern as {@link UserToolApprovalMessageSchema} / {@link UserToolResponseMessageSchema}.
*/
export const UserToolApprovalPolicyMessageSchema = z
.object({
type: z
.literal(EventType.USER_TOOL_APPROVAL_POLICY)
.describe('Sticky allow-session policy for matching tools (optional expiry).'),
policies: z.array(ToolApprovalPolicyItemSchema).min(1).describe('One or more (server, tool_name) policy entries.'),
})
.openapi('UserToolApprovalPolicyEvent');

export const TextContentPartSchema = z
.object({
type: z.literal('text').describe('Text content part.'),
Expand Down Expand Up @@ -373,6 +405,8 @@ export type AgentInfo = z.infer<typeof AgentInfoSchema>;
export type ApprovalDecision = z.infer<typeof ApprovalDecisionSchema>;
export type UserToolApprovalMessage = z.infer<typeof UserToolApprovalMessageSchema>;
export type UserToolResponseMessage = z.infer<typeof UserToolResponseMessageSchema>;
export type ToolApprovalPolicyItem = z.infer<typeof ToolApprovalPolicyItemSchema>;
export type UserToolApprovalPolicyMessage = z.infer<typeof UserToolApprovalPolicyMessageSchema>;
export type AgentApprovalDecisionMessage = z.infer<typeof AgentApprovalDecisionMessageSchema>;
export type InputTokensBreakdown = z.infer<typeof InputTokensBreakdownSchema>;
export type ModelMessageUsage = z.infer<typeof ModelMessageUsageSchema>;
Expand Down
2 changes: 2 additions & 0 deletions packages/trueforge-core/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export {
ToolResponseEventSchema,
ToolResponseRequiredEventSchema,
UserToolApprovalMessageSchema,
UserToolApprovalPolicyMessageSchema,
UserToolResponseMessageSchema,
newEventId,
} from './events/schema';
Expand All @@ -124,6 +125,7 @@ export type {
MCPServerInitInfo,
ThreadDoneEvent,
ThreadOverwriteContextEvent,
UserToolApprovalPolicyMessage,
} from './events/schema';
export { CompletionUsageSchema } from './llm/LLMTypes';
export type { CompletionUsage } from './llm/LLMTypes';
Expand Down
Loading