@withpica/mcp-server
Version:
MCP Server for withPICA — enables AI assistants to interact with your withPICA music catalog
664 lines • 37.6 kB
JavaScript
// Copyright (c) 2025-2026 Withpica Ltd. All rights reserved.
import { elicitBulkConfirmation, formatAsText, formatSuccess, formatStructuredList, RECORDING_SPLITS_WRITE_PROPERTIES, RECORDING_SPLITS_CREATE_REQUIRED, } from "@withpica/mcp-utils";
import { typedError } from "./error-envelope.js";
export class SplitSheetsTools {
pica;
constructor(pica) {
this.pica = pica;
}
/**
* Get all split sheet and recording split tools
*/
getTools() {
return [
{
definition: {
name: "pica_split_sheet_send",
// Sending goes through POST /api/admin/notifications/send-to-person,
// which requires write:people (route and floor). Pinned by
// lib/auth/__tests__/mcp-tool-scope-floor-contract.test.ts.
scope: "write:catalog",
alsoRequires: ["write:people"],
audiences: ["stdio", "http", "public"],
tier: "write",
description: "Send a split sheet to all credited writers on a work for review and signature. " +
"Notifies each writer via in-app notification, Telegram (if connected), and email. " +
"The split sheet must already be generated. Writers who are not PICA users get " +
"a collaboration invite email instead. " +
"For a work with no split sheet yet, use pica_split_sheet_generate; this one only sends an existing one.",
workflows: ["split-sheet-required"],
vernacular_kind: "specialist",
vernacular_reason: "Specialist context; not yet covered by the Creator Question Atlas — pending telemetry-driven addition per ADR-226 Phase 4 substrate writes.",
inputSchema: {
type: "object",
properties: {
work_id: {
type: "string",
description: "The work ID the split sheet belongs to",
},
split_sheet_id: {
type: "string",
description: "The split sheet ID to send",
},
message: {
type: "string",
description: "Optional custom message to include (default: 'please review and sign the split sheet')",
},
},
required: ["work_id", "split_sheet_id"],
},
},
executor: this.sendSplitSheet.bind(this),
},
{
definition: {
name: "pica_split_sheet_list",
scope: "read:catalog",
audiences: ["stdio", "http", "public"],
tier: "read",
description: "List split sheets for a work. Split sheets document how publishing " +
"royalties are divided among writers and publishers for a composition. " +
"Prefer pica_split_sheet_get when you have a split sheet id and its work id — it needs both, and returns the shares and signature status.",
workflows: ["split-sheet-required"],
vernacular_kind: "specialist",
vernacular_reason: "Specialist context; not yet covered by the Creator Question Atlas — pending telemetry-driven addition per ADR-226 Phase 4 substrate writes.",
inputSchema: {
type: "object",
properties: {
work_id: {
type: "string",
description: "The work ID to list split sheets for",
},
},
required: ["work_id"],
},
},
executor: this.listSplitSheets.bind(this),
},
{
definition: {
name: "pica_split_sheet_generate",
scope: "write:catalog",
audiences: ["stdio", "http", "public"],
tier: "draft",
description: "Use when the user asks: 'how do I split this with my co-writer?', 'set up a split sheet', 'generate a split sheet'. " +
"Generate a split sheet from a work's credits. This takes the existing " +
"writer credits on a work and creates a formal split sheet document. " +
"Credits must total 100% before generating. " +
"Prefer pica_split_sheet_send to put the generated sheet in front of the writers for signature.",
workflows: ["split-sheet-required"],
vernacular_kind: "creator-entry",
inputSchema: {
type: "object",
properties: {
work_id: {
type: "string",
description: "The work ID to generate a split sheet for",
},
},
required: ["work_id"],
},
},
executor: this.generateSplitSheet.bind(this),
},
{
definition: {
name: "pica_split_sheet_get",
scope: "read:catalog",
audiences: ["stdio", "http", "public"],
tier: "read",
description: "Get a specific split sheet with its full details including " +
"individual writer shares and signature status. " +
"Prefer pica_split_sheet_list when you do not have a split sheet id — it lists every sheet on a work.",
workflows: ["split-sheet-required"],
vernacular_kind: "specialist",
vernacular_reason: "Specialist context; not yet covered by the Creator Question Atlas — pending telemetry-driven addition per ADR-226 Phase 4 substrate writes.",
inputSchema: {
type: "object",
properties: {
work_id: {
type: "string",
description: "The work ID the split sheet belongs to",
},
split_sheet_id: {
type: "string",
description: "The split sheet ID",
},
},
required: ["work_id", "split_sheet_id"],
},
},
executor: this.getSplitSheet.bind(this),
},
{
definition: {
name: "pica_recording_splits_list",
scope: "read:catalog",
audiences: ["stdio", "http", "public"],
tier: "read",
description: "List ownership splits for a recording. These represent master rights " +
"ownership — who owns what percentage of the recorded performance " +
"(as opposed to the underlying composition). " +
"Prefer pica_recording_splits_create to record a new ownership share, pica_recording_splits_update or pica_recording_splits_end to fix one; this one only reads them.",
workflows: ["recording-splits-required"],
vernacular_kind: "specialist",
vernacular_reason: "Specialist context; not yet covered by the Creator Question Atlas — pending telemetry-driven addition per ADR-226 Phase 4 substrate writes.",
inputSchema: {
type: "object",
properties: {
recording_id: {
type: "string",
description: "The recording ID to list splits for",
},
},
required: ["recording_id"],
},
},
executor: this.listRecordingSplits.bind(this),
},
{
definition: {
name: "pica_recording_splits_create",
scope: "write:catalog",
audiences: ["stdio", "http", "public"],
tier: "write",
description: "Use when the user asks: 'I own 50% of the master', 'set the ownership split', " +
"'who owns this recording', 'record the master rights split'. " +
"Records master rights ownership percentage for a recording — NOT performance credits " +
"(who played/produced — that's pica_recording_credits_update). recording_id = UUID from " +
"pica_recordings_query; active shares for one split_type + territory may not pass 100%, and a person holds at most one share there.",
workflows: ["recording-splits-required"],
vernacular_kind: "specialist",
vernacular_reason: "Specialist context; not yet covered by the Creator Question Atlas — pending telemetry-driven addition per ADR-226 Phase 4 substrate writes.",
inputSchema: {
type: "object",
properties: {
...RECORDING_SPLITS_WRITE_PROPERTIES,
dry_run: {
type: "boolean",
description: "When true, persists nothing and returns { dry_run: true, would_affect, sample, existing_shares_checked, existing_shares, active_total, likely_refusal } — the row that would land, the shares already recorded for the same split_type + territory (active_total counts every active one), and the refusal code (RECORDING_SPLIT_TOTAL_EXCEEDED / RECORDING_SPLIT_EXISTS) when the write would be refused. existing_shares_checked is false (with existing_shares_error, and nothing predicted) when the shares could not be read. likely_refusal: null means neither share guard would refuse — not that every other check (access, person, confirmation) will pass. Stop and tell the user when likely_refusal is set.",
},
},
required: [...RECORDING_SPLITS_CREATE_REQUIRED],
},
previewMode: "dry_run",
},
executor: this.createRecordingSplit.bind(this),
},
{
definition: {
name: "pica_recording_splits_verify",
scope: "write:catalog",
audiences: ["stdio", "http", "public"],
tier: "write",
description: "Record that the split holder has ATTESTED to a recording split — " +
"sets verified_by to the attesting person. Only actionable from a " +
"signed-in human session: an API key or agent grant is refused with " +
"VERIFICATION_REQUIRES_HUMAN_SESSION, because an agent cannot attest " +
"on a person's behalf. Do not retry from an agent; tell the split " +
'holder to confirm it while signed in — "confirm my share" under ' +
"ownership splits at /inspect/recordings/<recording_id>#ownership-splits. " +
"Prefer pica_recording_splits_list to find the split_id first.",
workflows: ["recording-splits-required"],
vernacular_kind: "specialist",
vernacular_reason: "Specialist context; not yet covered by the Creator Question Atlas — pending telemetry-driven addition per ADR-226 Phase 4 substrate writes.",
inputSchema: {
type: "object",
properties: {
recording_id: {
type: "string",
description: "The recording ID",
},
split_id: {
type: "string",
description: "The split ID to verify",
},
},
required: ["recording_id", "split_id"],
},
},
executor: this.verifyRecordingSplit.bind(this),
},
{
definition: {
name: "pica_recording_splits_update",
scope: "write:catalog",
audiences: ["stdio", "http", "public"],
tier: "write",
description: "Use when the user says: 'that owner is wrong', 'link this share to <person>', " +
"'the master split should be 60/40', 'the unknown owner is actually X'. " +
"Edits a recorded master/mechanical/performance/sync ownership share in place: " +
"person_id (relink a share whose holder is unknown, or move it to the right person — a person in this " +
"organisation, not deleted), percentage, role, split_type, territory, start_date, end_date, notes. " +
"The 100% total is checked AFTER the change, with this row's old value left out, so fixing a wrong row " +
"never trips RECORDING_SPLIT_TOTAL_EXCEEDED on its own share. Changing who, how much, type, territory or " +
"dates clears the holder's confirmation (verification_cleared: true in the result) — tell the user the " +
"holder must confirm again. To make a share stop counting without deleting it, prefer " +
"pica_recording_splits_end. Find split_id with pica_recording_splits_list.",
workflows: ["recording-splits-required"],
vernacular_kind: "specialist",
vernacular_reason: "Specialist context; not yet covered by the Creator Question Atlas — pending telemetry-driven addition per ADR-226 Phase 4 substrate writes.",
inputSchema: {
type: "object",
properties: {
recording_id: {
type: "string",
description: "The recording the split is on (UUID).",
},
split_id: {
type: "string",
description: "The split row to edit (UUID, the `id` from pica_recording_splits_list).",
},
person_id: {
type: ["string", "null"],
description: "The person who holds this share (UUID from pica_people_query). Pass it to relink a row whose holder is null/unknown. null unlinks the holder.",
},
percentage: {
type: "number",
description: "The share, greater than 0 and at most 100.",
},
role: {
type: "string",
enum: [
"owner",
"performer",
"producer",
"engineer",
"mixer",
"masterer",
"label",
"other",
],
description: "The holder's capacity. Not a term: changing only this (or notes) keeps a confirmation.",
},
split_type: {
type: "string",
enum: ["master", "mechanical", "performance", "sync"],
description: "The kind of right this share is of.",
},
territory: {
type: "string",
description: "Where the share applies, e.g. worldwide.",
},
start_date: {
type: ["string", "null"],
description: "YYYY-MM-DD the share starts, or null.",
},
end_date: {
type: ["string", "null"],
description: "YYYY-MM-DD the share ended, or null for an active share. To end a share, prefer pica_recording_splits_end.",
},
notes: {
type: ["string", "null"],
description: "Free-text note on the share.",
},
},
required: ["recording_id", "split_id"],
},
},
executor: this.updateRecordingSplit.bind(this),
},
{
definition: {
name: "pica_recording_splits_end",
scope: "write:catalog",
audiences: ["stdio", "http", "public"],
tier: "write",
description: "Use when the user says: 'that share is wrong, take it off', 'they no longer own the master', " +
"'remove the unknown owner'. Ends a recorded ownership share: sets its end_date (default today) so it " +
"stops counting toward the 100% total. The row is kept as history — nothing is deleted. Refuses a share " +
"that has already ended (SPLIT_ALREADY_ENDED). Ending clears the holder's confirmation if there was one " +
"(verification_cleared: true). When the row only needs a different holder or percentage, prefer " +
"pica_recording_splits_update instead. Find split_id with pica_recording_splits_list.",
workflows: ["recording-splits-required"],
vernacular_kind: "specialist",
vernacular_reason: "Specialist context; not yet covered by the Creator Question Atlas — pending telemetry-driven addition per ADR-226 Phase 4 substrate writes.",
inputSchema: {
type: "object",
properties: {
recording_id: {
type: "string",
description: "The recording the split is on (UUID).",
},
split_id: {
type: "string",
description: "The split row to end (UUID, the `id` from pica_recording_splits_list).",
},
end_date: {
type: "string",
description: "YYYY-MM-DD the share ended — today or earlier, not before its start_date. Defaults to today.",
},
notes: {
type: ["string", "null"],
description: "Optional note on why the share ended.",
},
},
required: ["recording_id", "split_id"],
},
},
executor: this.endRecordingSplit.bind(this),
},
];
}
// ── Send Split Sheet ─────────────────────────────────────────────────
async sendSplitSheet(args) {
// 1. Verify split sheet exists
const sheet = await this.pica.splitSheets.get(args.work_id, args.split_sheet_id);
if (!sheet) {
// typedError, not formatAsText({error}): without isError the dispatcher
// reads this refusal as a success and mcp_audit_log.error_code is NULL.
// `sent: false` rides along as an extra body field. See
// ./error-envelope.ts.
return typedError("SPLIT_SHEET_NOT_FOUND", "Split sheet not found", {
sent: false,
});
}
// 2. Resolve the composition collaborators (writers + publishers) — the
// parties to a split sheet. These live in `work_collaborators` (the same
// source pica_split_sheet_generate resolves from); the /collaborators
// route returns `{ collaborators: [...] }`.
// Previously this read `credits.listForWork(...).data`, a double bug
// (ops-issue 7c1a3d5c): (a) wrong source — the /credits route merges
// master `recording_credits`, so a split sheet would notify performers;
// (b) wrong key — the route returns `.collaborators`/`.credits`, never
// `.data`, so writers came back empty and the tool falsely reported
// "No credits found" even when verified writers existed.
const collaboratorsResult = await this.pica.credits.listCollaborators(args.work_id);
const writers = Array.isArray(collaboratorsResult)
? collaboratorsResult
: collaboratorsResult?.collaborators || [];
if (writers.length === 0) {
return typedError("NO_WRITERS_ON_WORK", "No credits found on this work. Add writers before sending the split sheet.", { sent: false });
}
// 3. Notify each writer
const message = args.message?.trim() ||
"please review and sign the split sheet for this work";
const results = [];
for (const credit of writers) {
if (!credit.person_id)
continue;
try {
const result = await this.pica.notifications.sendToPerson({
person_id: credit.person_id,
message: `${message} — your share: ${credit.splits ?? "?"}%`,
work_id: args.work_id,
notification_type: "split_sheet_review",
});
const data = result.data || result;
results.push({
person_id: credit.person_id,
name: data.person_name || credit.person_name || "Unknown",
delivered: data.delivered,
channels: data.channels,
reason: data.reason,
});
}
catch {
results.push({
person_id: credit.person_id,
name: credit.person_name || "Unknown",
delivered: false,
reason: "notification_failed",
});
}
}
const delivered = results.filter((r) => r.delivered);
const notPicaUsers = results.filter((r) => r.reason === "not_a_pica_user");
const failed = results.filter((r) => !r.delivered && r.reason !== "not_a_pica_user");
const summary = [
`Split sheet sent to ${delivered.length}/${results.length} writers`,
delivered.length > 0
? `Delivered: ${delivered.map((r) => r.name).join(", ")}`
: null,
notPicaUsers.length > 0
? `Not PICA users (use pica_collaborators_invite): ${notPicaUsers.map((r) => r.name).join(", ")}`
: null,
failed.length > 0
? `Failed: ${failed.map((r) => r.name).join(", ")}`
: null,
]
.filter(Boolean)
.join(". ");
return formatSuccess(summary, {
split_sheet_id: args.split_sheet_id,
work_id: args.work_id,
results,
});
}
// ── Split Sheet Methods ──────────────────────────────────────────────
async listSplitSheets(args) {
const sheets = await this.pica.splitSheets.listForWork(args.work_id);
const data = Array.isArray(sheets) ? sheets : (sheets?.data ?? [sheets]);
return formatStructuredList(data, "split_sheet", {
work_id: args.work_id,
total: data.length,
});
}
async generateSplitSheet(args) {
try {
const sheet = await this.pica.splitSheets.generate(args.work_id);
return formatSuccess("Split sheet generated successfully", sheet);
}
catch (error) {
// Route returns 400 VALIDATION_ERROR when validateTotals throws (splits
// don't sum to 100%, publisher splits partially exist, etc.) — see
// APP_USER_INPUT_PATTERNS in lib/utils/error.ts. Without this catch the
// MCP framework stamps the ApiError as UNKNOWN_ERROR, giving agents zero
// actionable signal. Surface the message so agents can tell the user
// exactly what to fix. ops_issue 899a3d63.
const message = error instanceof Error ? error.message : String(error);
return {
content: [
{ type: "text", text: `split sheet generation failed: ${message}` },
],
isError: true,
};
}
}
async getSplitSheet(args) {
const sheet = await this.pica.splitSheets.get(args.work_id, args.split_sheet_id);
return formatAsText(sheet);
}
// ── Recording Split Methods ──────────────────────────────────────────
async listRecordingSplits(args) {
const splits = await this.pica.recordingSplits.list(args.recording_id);
const data = Array.isArray(splits) ? splits : (splits?.data ?? [splits]);
return formatStructuredList(data, "recording_split", {
recording_id: args.recording_id,
total: data.length,
});
}
/**
* dry_run for pica_recording_splits_create: the row that would land, the
* shares already on the recording for the same split_type + territory, and
* `likely_refusal` when the write would be refused.
*
* It is a PREDICTION, not the guard. It restates validate_recording_splits
* (migration 20260917180000) over the listed rows: an ended new row is not
* checked; otherwise the active shares other than this person's own row plus
* this share must not pass 100% (compared in hundredths, as numeric(5,2)
* stores them — a float sum of 36.06 + 35.24 + 21 + 7.7 is not over 100).
* The total is checked BEFORE the unique key, the order the database uses,
* so a share that is both over 100% and on a taken key predicts
* RECORDING_SPLIT_TOTAL_EXCEEDED, as the write answers. Not seen: rows past
* the list route's 100-row cap, and any later change to the trigger. Pinned
* against the real write by pica-holdout-tests
* scenarios/catalog/recording-split-readd-is-not-a-refusal.spec.ts (case 7)
* and split-sheets.test.ts.
*/
async previewRecordingSplit(recordingId, data) {
const splitType = data.split_type;
const territory = data.territory || "worldwide";
const hundredths = (p) => Math.round(Number(p) * 100 + 1e-6);
const asPct = (h) => h / 100;
const attempted = hundredths(data.percentage);
const base = {
dry_run: true,
would_affect: [recordingId],
sample: { recording_id: recordingId, ...data },
target_count: 1,
};
const unchecked = (reason) => formatSuccess(`Dry run — would create a recording split on ${recordingId}. existing shares could NOT be read (${reason}), so this preview does not say whether the write would be refused.`, {
...base,
existing_shares_checked: false,
existing_shares_error: reason,
});
let rows;
try {
const listed = await this.pica.recordingSplits.list(recordingId);
if (Array.isArray(listed))
rows = listed;
else if (Array.isArray(listed?.data))
rows = listed.data;
else
return unchecked("the splits list returned an unexpected shape");
}
catch (err) {
return unchecked(err instanceof Error ? err.message : String(err));
}
const sameBucket = rows.filter((r) => r.split_type === splitType && r.territory === territory);
const isActive = (r) => r.end_date == null;
// The unique index is (recording, person, split_type, territory) over
// ACTIVE rows only (end_date IS NULL — migration 20261004140000), so only
// this person's active share takes the place; one that has ended (any
// end_date, today included) does not, and re-adding them is a new share.
// A row written already ended is outside the index too, so it never
// collides (and, below, is not checked against the total). Before that
// migration applies, the table-wide constraint still refuses a re-add
// after an ended share, so this preview under-predicts there.
// existing_end_date below is therefore always null; kept so the response
// shape does not change.
const samePerson = data.person_id
? sameBucket.find((r) => r.person_id === data.person_id && isActive(r))
: undefined;
const activeTotal = sameBucket
.filter(isActive)
.reduce((sum, r) => sum + hundredths(r.percentage || 0), 0);
const replacing = samePerson ? hundredths(samePerson.percentage) : 0;
const resulting = activeTotal - replacing + attempted;
let likelyRefusal = null;
if (data.end_date == null && resulting > 10000) {
likelyRefusal = {
code: "RECORDING_SPLIT_TOTAL_EXCEEDED",
active_total: asPct(activeTotal),
attempted: asPct(attempted),
replacing: asPct(replacing),
resulting_total: asPct(resulting),
};
}
else if (samePerson && data.end_date == null) {
likelyRefusal = {
code: "RECORDING_SPLIT_EXISTS",
existing_split_id: samePerson.id,
existing_percentage: asPct(hundredths(samePerson.percentage)),
existing_end_date: samePerson.end_date ?? null,
};
}
const verdict = likelyRefusal
? `the write would be refused (${likelyRefusal.code}) — nothing to create.`
: `active ${splitType} shares here total ${asPct(activeTotal)}%, ${asPct(resulting)}% with this one.`;
return formatSuccess(`Dry run — would create a recording split on ${recordingId}: ${verdict}`, {
...base,
existing_shares_checked: true,
existing_shares: sameBucket.map((r) => ({
id: r.id,
person_id: r.person_id,
role: r.role,
percentage: Number(r.percentage),
end_date: r.end_date ?? null,
})),
active_total: asPct(activeTotal),
likely_refusal: likelyRefusal,
});
}
async createRecordingSplit(args, ctx) {
const { recording_id, dry_run, ...data } = args;
// ADR-199 Stream 5b — dry_run preview before mutation. The operation
// creates exactly one row, so the preview is that row plus the shares
// already recorded beside it. Until 2026-09-17 it was the echo alone, so
// it "succeeded" for a share the write then refused — an agent read that
// as the tool contradicting itself (ops_issue 7215bcdb).
if (dry_run === true) {
return this.previewRecordingSplit(recording_id, data);
}
// ADR-200 Phase 1 — Class D form-confirmation gate. Recording
// splits drive royalty payouts; agent-issued writes need explicit
// user confirmation when the client supports the form primitive.
const role = data.role ?? "owner";
const percentage = data.percentage;
const gate = await elicitBulkConfirmation({
server: ctx?.server,
args,
action: "Create recording split",
impactSummary: `${role} ${percentage != null ? percentage + "%" : "split"} on recording ${recording_id}`,
warning: "Recording splits drive master royalty payouts and downstream credit gating. Confirm the percentage is correct before proceeding.",
});
if (gate.kind === "abort")
return gate.result;
const split = await this.pica.recordingSplits.create(recording_id, data);
return formatSuccess("Recording split created successfully", split);
}
async verifyRecordingSplit(args) {
const result = await this.pica.recordingSplits.verify(args.recording_id, args.split_id);
return formatSuccess("Recording split verified successfully", result);
}
/**
* Turn the edit / end route's envelope into the tool result. The message
* says plainly when a confirmation was cleared, because the agent has to
* tell the user the holder must confirm again.
*/
editResult(act, envelope) {
const changed = envelope.changed_fields ?? [];
const cleared = envelope.verification_cleared === true;
const parts = [
act === "ended"
? `Recording split ended on ${envelope.data?.end_date ?? "today"} — it no longer counts toward the total.`
: changed.length
? `Recording split updated (${changed.join(", ")}).`
: "Recording split saved — no stored value changed.",
];
if (cleared) {
parts.push('This share had been confirmed by its holder; because its terms (holder, percentage, type, territory or dates) changed, that confirmation was cleared. Tell the user the holder needs to confirm it again (signed in, "confirm my share" on the recording page).');
}
return formatSuccess(parts.join(" "), {
split: envelope.data,
previous: envelope.previous ?? null,
changed_fields: changed,
verification_cleared: cleared,
});
}
async updateRecordingSplit(args, ctx) {
// dry_run is not an input here; drop it if an agent sends it (the
// confirmation gate's cancel text suggests it) rather than 400 on it.
const { recording_id, split_id, dry_run: _dryRun, ...fields } = args;
// ADR-200 Class D gate, as on create: ownership shares drive payouts.
const gate = await elicitBulkConfirmation({
server: ctx?.server,
args,
action: "Edit recording split",
impactSummary: `split ${split_id} on recording ${recording_id}: ${Object.keys(fields).join(", ") || "no fields"}`,
warning: "Recording splits drive master royalty payouts. Changing who holds a share, or how much, clears the holder's confirmation.",
});
if (gate.kind === "abort")
return gate.result;
const envelope = await this.pica.recordingSplits.edit(recording_id, split_id, fields);
return this.editResult("updated", envelope);
}
async endRecordingSplit(args, ctx) {
// dry_run is not an input here; drop it if an agent sends it (the
// confirmation gate's cancel text suggests it) rather than 400 on it.
const { recording_id, split_id, dry_run: _dryRun, ...fields } = args;
const gate = await elicitBulkConfirmation({
server: ctx?.server,
args,
action: "End recording split",
impactSummary: `end split ${split_id} on recording ${recording_id}${fields.end_date ? ` on ${fields.end_date}` : " today"}`,
warning: "An ended share stops counting toward the recording's ownership total. The row is kept as history.",
});
if (gate.kind === "abort")
return gate.result;
const envelope = await this.pica.recordingSplits.end(recording_id, split_id, fields);
return this.editResult("ended", envelope);
}
}
//# sourceMappingURL=split-sheets.js.map