update
This commit is contained in:
1 parent
740d0812ee
commit
97033dfabd
68 files changed
+2608
-328
No files matched your search
@@ -14,7 +14,7 @@
|
||||
*/
|
||||
|
||||
import type { AppDatabase } from "@main/database";
|
||||
import { approvalInputSchema } from "@shared/contracts";
|
||||
import { approvalInputSchema, mergeApprovalAttachments } from "@shared/contracts";
|
||||
import { resolveApprovalDecision } from "@main/approval-policy";
|
||||
import { shouldAutoApprove } from "@main/execution-policy";
|
||||
import { UserInputError, UpstreamError, classifyUnknownError } from "@main/errors";
|
||||
@@ -90,6 +90,25 @@ export function handleFileApprovalToolCall(
|
||||
}
|
||||
}
|
||||
|
||||
// Fold schema-level documentIds / workProductIds into payloadJson so the
|
||||
// desktop inspector has a single source of truth. payloadJson remains the
|
||||
// escape hatch for type-specific structured fields. Malformed payloadJson
|
||||
// (non-JSON or non-object) is a producer bug — surface it to the agent as
|
||||
// UserInputError so the tool call can be corrected, not silently stashed.
|
||||
let mergedPayloadJson: string | null;
|
||||
try {
|
||||
mergedPayloadJson = mergeApprovalAttachments(
|
||||
input.payloadJson,
|
||||
input.documentIds,
|
||||
input.workProductIds,
|
||||
);
|
||||
} catch (error) {
|
||||
throw new UserInputError({
|
||||
code: "file_approval.payload_json.invalid",
|
||||
message: error instanceof Error ? error.message : "payloadJson must be a JSON object or null.",
|
||||
});
|
||||
}
|
||||
|
||||
let approvalId: string;
|
||||
try {
|
||||
approvalId = db.approvalsMod.requestApproval({
|
||||
@@ -100,7 +119,7 @@ export function handleFileApprovalToolCall(
|
||||
relatedAgentId: input.relatedAgentId ?? null,
|
||||
payloadSummary: input.payloadSummary,
|
||||
impactSummary: input.impactSummary,
|
||||
payloadJson: input.payloadJson ?? null,
|
||||
payloadJson: mergedPayloadJson,
|
||||
});
|
||||
} catch (error) {
|
||||
throw classifyUnknownError(error, {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod";
|
||||
import log from "electron-log/main.js";
|
||||
import { approvalInputSchema } from "@shared/contracts";
|
||||
import { approvalInputSchema, mergeApprovalAttachments } from "@shared/contracts";
|
||||
import { resolveApprovalDecision } from "../approval-policy";
|
||||
import { shouldAutoApprove } from "../execution-policy";
|
||||
import { incrementDeprecationCall } from "./deprecation-counter";
|
||||
@@ -94,6 +94,27 @@ export function registerApprovalRoutes(server: AgentApiServer) {
|
||||
}
|
||||
}
|
||||
|
||||
// Fold the schema-level documentIds / workProductIds into payloadJson so
|
||||
// the ApprovalInspector and extractApprovalDocumentReferences can read a
|
||||
// single source of truth. Legacy callers that stuffed documentIds inside
|
||||
// payloadJson keep working unchanged. Malformed payloadJson (non-JSON
|
||||
// or non-object) surfaces as 400 at the API boundary — no silent
|
||||
// rawPayload stashing, because that hid producer bugs from the operator.
|
||||
let mergedPayloadJson: string | null;
|
||||
try {
|
||||
mergedPayloadJson = mergeApprovalAttachments(
|
||||
input.payloadJson,
|
||||
input.documentIds,
|
||||
input.workProductIds,
|
||||
);
|
||||
} catch (error) {
|
||||
server.json(res, 400, {
|
||||
error: error instanceof Error ? error.message : "Invalid approval payloadJson",
|
||||
code: "INVALID_PAYLOAD_JSON",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let id: string;
|
||||
try {
|
||||
id = db.approvalsMod.requestApproval({
|
||||
@@ -104,7 +125,7 @@ export function registerApprovalRoutes(server: AgentApiServer) {
|
||||
relatedAgentId: input.relatedAgentId ?? null,
|
||||
payloadSummary: input.payloadSummary,
|
||||
impactSummary: input.impactSummary,
|
||||
payloadJson: input.payloadJson ?? null,
|
||||
payloadJson: mergedPayloadJson,
|
||||
});
|
||||
} catch (error) {
|
||||
server.json(res, 422, { error: error instanceof Error ? error.message : "Approval could not be created" });
|
||||
|
||||
@@ -5,7 +5,9 @@ Body: {
|
||||
"relatedTaskId": "{taskId — what task is this for}",
|
||||
"requestedByAgentId": "{{agentId}}",
|
||||
"payloadSummary": "Clear description of what you need and why",
|
||||
"impactSummary": "What happens if this is approved vs denied"
|
||||
"impactSummary": "What happens if this is approved vs denied",
|
||||
"documentIds": ["{docUuid1}", "{docUuid2}"],
|
||||
"workProductIds": ["{workProductUuid}"]
|
||||
}
|
||||
Approval types:
|
||||
- secret_access: Need API keys, credentials, or account access
|
||||
@@ -13,6 +15,30 @@ Approval types:
|
||||
- approve_ceo_strategy: CEO submitting org strategy for Board review
|
||||
- dangerous_command: Irreversible action that needs human sign-off
|
||||
|
||||
Attaching artifacts:
|
||||
- `documentIds` — UUIDs of existing company documents. The board inspector
|
||||
renders each as a clickable row on the first screen of the approval modal,
|
||||
so reviewers can open and edit inline.
|
||||
- `workProductIds` — UUIDs of existing work products (files on disk,
|
||||
external URLs). The inspector surfaces each row with a click-to-open
|
||||
action that hands off to the operator's default application.
|
||||
- Prefer these schema-typed arrays over stuffing `{documentIds:[...]}` into
|
||||
`payloadJson`. Agents using the `file_approval` internal tool pass the
|
||||
same fields directly as arguments.
|
||||
|
||||
Example — deliverable_review that asks the CEO to sign off on a spec and an
|
||||
exported PDF:
|
||||
```
|
||||
{
|
||||
"type": "deliverable_review",
|
||||
"relatedTaskId": "8f7a...",
|
||||
"payloadSummary": "Q2 launch plan ready for CEO review",
|
||||
"impactSummary": "Unblocks the kickoff email if approved; revision_requested loops back to me.",
|
||||
"documentIds": ["2c9d...e41a"],
|
||||
"workProductIds": ["55ab...90ff"]
|
||||
}
|
||||
```
|
||||
|
||||
### Check Approval Status
|
||||
GET /api/approvals/{approvalId}
|
||||
|
||||
|
||||
@@ -76,7 +76,19 @@ export const fileApprovalSchema = Type.Object({
|
||||
payloadJson: Type.Optional(
|
||||
Type.String({
|
||||
description:
|
||||
"Optional JSON string carrying structured approval payload. Must be valid JSON if provided.",
|
||||
"Optional JSON string carrying type-specific structured payload (hire proposal, social draft, etc.). Must be valid JSON if provided. For attaching existing documents or work products, use documentIds / workProductIds — the board UI renders those directly on the first screen.",
|
||||
}),
|
||||
),
|
||||
documentIds: Type.Optional(
|
||||
Type.Array(Type.String(), {
|
||||
description:
|
||||
"UUIDs of existing company documents to attach to this approval. The board inspector surfaces each attachment on the first screen of the approval modal. Prefer this over jamming {documentIds:[...]} into payloadJson.",
|
||||
}),
|
||||
),
|
||||
workProductIds: Type.Optional(
|
||||
Type.Array(Type.String(), {
|
||||
description:
|
||||
"UUIDs of existing work products (files on disk, external URLs) to attach to this approval. The board inspector renders each row as a clickable link that opens in the operator's default application.",
|
||||
}),
|
||||
),
|
||||
relatedTaskId: Type.Optional(
|
||||
@@ -104,7 +116,7 @@ export function createFileApprovalTool(rpc: WorkerRpcClient, runId: string): Age
|
||||
name: FILE_APPROVAL_TOOL_NAME,
|
||||
label: "file approval",
|
||||
description:
|
||||
"File an approval request with the company board. Use for 'approve_ceo_strategy' after filing initial hires, or for any other decision that requires board sign-off. The request is auto-approved when the company is in hands-off mode; otherwise it waits for the human operator.",
|
||||
"File an approval request with the company board. Use for 'approve_ceo_strategy' after filing initial hires, or for any other decision that requires board sign-off. When the approval references existing documents or work products (e.g. deliverable_review, document_review), pass their UUIDs via documentIds / workProductIds so the board inspector can render them as clickable attachments on the first screen — do not bury IDs inside payloadJson. The request is auto-approved when the company is in hands-off mode; otherwise it waits for the human operator.",
|
||||
parameters: fileApprovalSchema,
|
||||
async execute(toolCallId, params, _signal?, _onUpdate?) {
|
||||
const payload: InvokeInternalToolRpcPayload = {
|
||||
|
||||
Reference in new issue
Block a user