@integrity-labs/xero-broker 0.1.2 → 0.1.4

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.
Files changed (2) hide show
  1. package/dist/index.js +37 -5
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -20987,10 +20987,41 @@ var StdioServerTransport = class {
20987
20987
 
20988
20988
  // src/turn-initiator-marker.ts
20989
20989
  import { readFileSync } from "fs";
20990
+ import { dirname, join } from "path";
20990
20991
  var TURN_INITIATOR_MAX_AGE_MS = 5 * 60 * 1e3;
20992
+ var TURN_INITIATOR_LEDGER_FILENAME = ".turn-initiator-ledger.json";
20993
+ function readTurnInitiatorFromLedger(file, maxAgeMs) {
20994
+ const ledgerFile = join(dirname(file), TURN_INITIATOR_LEDGER_FILENAME);
20995
+ let entries;
20996
+ try {
20997
+ const parsed = JSON.parse(readFileSync(ledgerFile, "utf8"));
20998
+ if (!parsed || parsed.v !== 1 || !Array.isArray(parsed.entries)) return "no-ledger";
20999
+ entries = parsed.entries;
21000
+ } catch {
21001
+ return "no-ledger";
21002
+ }
21003
+ const now = Date.now();
21004
+ const fresh = entries.filter(
21005
+ (e) => e && typeof e.channel === "string" && e.channel && typeof e.sender_id === "string" && e.sender_id && typeof e.ts === "number" && Number.isFinite(e.ts) && now - e.ts >= 0 && now - e.ts <= maxAgeMs
21006
+ );
21007
+ if (fresh.length === 1) {
21008
+ const e = fresh[0];
21009
+ return { channel: e.channel, sender_id: e.sender_id };
21010
+ }
21011
+ if (fresh.length >= 2) {
21012
+ process.stderr.write(
21013
+ `turn-initiator: ambiguous (${fresh.length} fresh senders), withholding initiator
21014
+ `
21015
+ );
21016
+ return null;
21017
+ }
21018
+ return "no-ledger";
21019
+ }
20991
21020
  function readTurnInitiator(maxAgeMs = TURN_INITIATOR_MAX_AGE_MS) {
20992
21021
  const file = process.env["AGT_TURN_INITIATOR_FILE"];
20993
21022
  if (!file) return null;
21023
+ const fromLedger = readTurnInitiatorFromLedger(file, maxAgeMs);
21024
+ if (fromLedger !== "no-ledger") return fromLedger;
20994
21025
  try {
20995
21026
  const raw = readFileSync(file, "utf8");
20996
21027
  const m = JSON.parse(raw);
@@ -21159,17 +21190,18 @@ var XERO_WRITE_VERBS = [
21159
21190
  "xero.bill.approve",
21160
21191
  "xero.invoice.void",
21161
21192
  "xero.contact.merge",
21162
- "xero.bank_transaction.delete"
21193
+ "xero.bank_transaction.delete",
21194
+ "xero.manual_journal.create"
21163
21195
  ];
21164
21196
  var verbSchema = external_exports.enum(XERO_WRITE_VERBS).describe(
21165
- "The Xero write operation to perform. One of: xero.payment.create (pay an invoice/bill \u2014 moves money), xero.bill.create (create a payable), xero.bill.approve (DRAFT/SUBMITTED \u2192 AUTHORISED), xero.invoice.void (irreversible), xero.contact.merge (irreversible), xero.bank_transaction.delete (irreversible)."
21197
+ "The Xero write operation to perform. One of: xero.payment.create (pay an invoice/bill \u2014 moves money), xero.bill.create (create a payable), xero.bill.approve (DRAFT/SUBMITTED \u2192 AUTHORISED), xero.invoice.void (irreversible), xero.contact.merge (irreversible), xero.bank_transaction.delete (irreversible), xero.manual_journal.create (post a multi-line GL entry \u2014 reclasses, accruals, corrections; debits must equal credits)."
21166
21198
  );
21167
21199
  var payloadSchema = external_exports.object({
21168
21200
  tenant_id: external_exports.string().min(1).max(128).describe(
21169
21201
  "Xero organisation id (the Xero-Tenant-Id header value) the write lands in. NOT the Augmented team. Required \u2014 a missing tenant risks writing to the wrong set of books."
21170
21202
  )
21171
21203
  }).passthrough().describe(
21172
- "Verb-specific fields. xero.payment.create: { invoice_id, account_id, amount, currency?, counterparty? }. xero.bill.create: { contact_id, amount, currency?, counterparty?, line_items_preview }. xero.bill.approve: { invoice_id, amount?, currency?, counterparty? }. xero.invoice.void: { invoice_id, invoice_number?, amount?, counterparty? }. xero.contact.merge: { source_contact_id, target_contact_id, source_name?, target_name? }. xero.bank_transaction.delete: { bank_transaction_id, amount?, counterparty? }. Always include tenant_id."
21204
+ "Verb-specific fields. xero.payment.create: { invoice_id, account_id, amount, currency?, counterparty? }. xero.bill.create: { contact_id, amount, currency?, counterparty?, line_items_preview }. xero.bill.approve: { invoice_id, amount?, currency?, counterparty? }. xero.invoice.void: { invoice_id, invoice_number?, amount?, counterparty? }. xero.contact.merge: { source_contact_id, target_contact_id, source_name?, target_name? }. xero.bank_transaction.delete: { bank_transaction_id, amount?, counterparty? }. xero.manual_journal.create: { narration, journal_lines: [{ account_code, line_amount (positive=debit, negative=credit), description?, tax_type? }], date? (YYYY-MM-DD), status? (DRAFT|POSTED, default DRAFT), amount?, currency? } \u2014 lines must balance to zero. Always include tenant_id."
21173
21205
  );
21174
21206
  var reasonSchema = external_exports.string().min(1).max(2e3).describe(
21175
21207
  "Why the agent needs to perform this write. Surfaced verbatim on the approval card for the human reviewer. Mandatory."
@@ -21207,7 +21239,7 @@ var awaitDecisionSchema = external_exports.object({
21207
21239
  // package.json
21208
21240
  var package_default = {
21209
21241
  name: "@integrity-labs/xero-broker",
21210
- version: "0.1.2",
21242
+ version: "0.1.4",
21211
21243
  description: "Xero Broker \u2014 MCP server that routes money- and ledger-touching Xero writes through approval-core (HITL). Sibling to cloud-broker / channel-broker; the only write path for an agent once the vendor xero-mcp-server's write tools are stripped. ENG-4922.",
21212
21244
  type: "module",
21213
21245
  bin: {
@@ -21287,7 +21319,7 @@ server.tool(
21287
21319
  );
21288
21320
  server.tool(
21289
21321
  "xero_request_approval",
21290
- 'Perform a Xero write (pay an invoice/bill, create or approve a bill, void an invoice, merge contacts, delete a bank transaction). This is the ONLY path you have to a Xero write \u2014 the vendor Xero tools are read-only on your host. The broker hard-denies (team denylist), auto-approves (rare, only if team policy auto-approves the verb tier), or routes to a human approver. On approval the broker executes the Xero call itself and returns the result. Returns { request_id, status, expires_at, denial_reason?, notification_status? }. status="auto_approve" or "active" means the write already happened (result_payload describes it). status="pending" means a human was paged via Slack \u2014 DO NOT poll; the broker pushes the resolution to you via direct-chat the moment it lands. status="denied" means the request was rejected (denial_reason explains). Pass { verb, payload, reason } and optionally ttl_seconds + source_context; always include payload.tenant_id. agent_id and run_id auto-fill from MCP env. Surface a brief, jargon-free acknowledgement to the user (e.g. "I need approval to pay that invoice \u2014 pinged an admin, will let you know") before returning control on a pending status. NEVER expose broker mechanics ("broker", "approval card", "request_id") in user-facing messages, and NEVER paste raw amounts into operator logs \u2014 the approval card already shows the amount to the human reviewer.',
21322
+ 'Perform a Xero write (pay an invoice/bill, create or approve a bill, void an invoice, merge contacts, delete a bank transaction, post a manual journal). This is the ONLY path you have to a Xero write \u2014 the vendor Xero tools are read-only on your host. The broker hard-denies (team denylist), auto-approves (rare, only if team policy auto-approves the verb tier), or routes to a human approver. On approval the broker executes the Xero call itself and returns the result. Returns { request_id, status, expires_at, denial_reason?, notification_status? }. status="auto_approve" or "active" means the write already happened (result_payload describes it). status="pending" means a human was paged via Slack \u2014 DO NOT poll; the broker pushes the resolution to you via direct-chat the moment it lands. status="denied" means the request was rejected (denial_reason explains). Pass { verb, payload, reason } and optionally ttl_seconds + source_context; always include payload.tenant_id. agent_id and run_id auto-fill from MCP env. Surface a brief, jargon-free acknowledgement to the user (e.g. "I need approval to pay that invoice \u2014 pinged an admin, will let you know") before returning control on a pending status. NEVER expose broker mechanics ("broker", "approval card", "request_id") in user-facing messages, and NEVER paste raw amounts into operator logs \u2014 the approval card already shows the amount to the human reviewer.',
21291
21323
  requestApprovalSchema.shape,
21292
21324
  async (args) => {
21293
21325
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@integrity-labs/xero-broker",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Xero Broker — MCP server that routes money- and ledger-touching Xero writes through approval-core (HITL). Sibling to cloud-broker / channel-broker; the only write path for an agent once the vendor xero-mcp-server's write tools are stripped. ENG-4922.",
5
5
  "type": "module",
6
6
  "bin": {