From 62184ae06af6c6f7f5780c7d4583543ae508b151 Mon Sep 17 00:00:00 2001 From: alex-w-99 Date: Wed, 22 Jul 2026 06:06:43 +0000 Subject: [PATCH 1/2] Align contacts with organization-wide access --- .github/workflows/tests.yml | 6 +- CHANGELOG.md | 6 +- README.md | 4 +- package-lock.json | 2 +- package.json | 2 +- skills/inkbox-contact-lookup/SKILL.md | 12 +-- skills/inkbox-identity-access/SKILL.md | 22 ++-- src/gateway/contacts.ts | 5 +- src/tools/access.ts | 95 +--------------- src/tools/contacts.ts | 17 ++- tests/contract/tool-vocabulary.test.ts | 7 +- tests/unit/access.test.ts | 143 +------------------------ 12 files changed, 38 insertions(+), 283 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 48236a4..46a067e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v7 with: repository: inkbox-ai/inkbox - ref: 8f4b37c56cd153a2acc6cfabe27dbbeb43510f00 + ref: 199bbd27c8dab2f70e379de52ca9cc910b0e141d path: .ci/inkbox - uses: actions/setup-node@v7 with: @@ -39,7 +39,7 @@ jobs: - uses: actions/checkout@v7 with: repository: inkbox-ai/inkbox - ref: 8f4b37c56cd153a2acc6cfabe27dbbeb43510f00 + ref: 199bbd27c8dab2f70e379de52ca9cc910b0e141d path: .ci/inkbox - uses: actions/setup-node@v7 with: @@ -65,7 +65,7 @@ jobs: - uses: actions/checkout@v7 with: repository: inkbox-ai/inkbox - ref: 8f4b37c56cd153a2acc6cfabe27dbbeb43510f00 + ref: 199bbd27c8dab2f70e379de52ca9cc910b0e141d path: .ci/inkbox - uses: actions/setup-node@v7 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index fde581c..cf5c0c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,9 @@ Initial release. -- Requires `@inkbox/sdk` 0.5.0 or newer. -- 56 `inkbox_*` tools across email, SMS/MMS, iMessage, calls, contacts, notes, - contact rules, access grants, encrypted vault, and diagnostics. 24 are +- Requires `@inkbox/sdk` 0.5.1 or newer. +- 48 `inkbox_*` tools across email, SMS/MMS, iMessage, calls, contacts, notes, + contact rules, note access grants, encrypted vault, and diagnostics. 27 are enabled by default; the rest are opt-in via the `tools.enable` plugin option (`inkbox_doctor` reports what is off and how to enable it). - Outbound sends and calls gate through opencode's native permission prompts, diff --git a/README.md b/README.md index 8c8ae33..96213ba 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ groups are enabled. ## Tools -27 tools are enabled by default; 24 more are opt-in (see +27 tools are enabled by default; 21 more are opt-in (see [Enabling more tools](#enabling-more-tools)). Names are stable — treat renames as breaking. @@ -124,7 +124,7 @@ as breaking. | `contacts` | `inkbox_lookup_contact`, `inkbox_get_contact`, `inkbox_list_contacts`, `inkbox_create_contact`, `inkbox_update_contact`, `inkbox_delete_contact` | — | | `notes` | `inkbox_list_notes`, `inkbox_get_note`, `inkbox_create_note` | `inkbox_update_note`, `inkbox_delete_note` | | `contact-rules` | `inkbox_list_mail_contact_rules`, `inkbox_list_phone_contact_rules`, `inkbox_list_imessage_contact_rules` (read-only: rule changes are console-only) | — | -| `access` | — | `inkbox_list_contact_access`, `inkbox_grant_contact_access`, `inkbox_revoke_contact_access`, `inkbox_list_note_access`, `inkbox_grant_note_access`, `inkbox_revoke_note_access` | +| `access` | — | `inkbox_list_note_access`, `inkbox_grant_note_access`, `inkbox_revoke_note_access` | | `vault` | — | `inkbox_credentials_list`, and by exact name only: `inkbox_credentials_get_login`, `inkbox_credentials_get_api_key`, `inkbox_credentials_get_ssh_key`, `inkbox_totp_code` | | `diagnostics` | `inkbox_whoami`, `inkbox_doctor` | — | diff --git a/package-lock.json b/package-lock.json index 6b57bc8..7859285 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "license": "MIT", "dependencies": { - "@inkbox/sdk": ">=0.5.0 <1.0.0", + "@inkbox/sdk": ">=0.5.1 <1.0.0", "@opencode-ai/sdk": ">=1.17.18 <1.19.0", "ws": "^8.21.0", "zod": "4.1.8" diff --git a/package.json b/package.json index ac8465a..3bb4504 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "opencode": ">=1.15.0 <1.19.0" }, "dependencies": { - "@inkbox/sdk": ">=0.5.0 <1.0.0", + "@inkbox/sdk": ">=0.5.1 <1.0.0", "@opencode-ai/sdk": ">=1.17.18 <1.19.0", "ws": "^8.21.0", "zod": "4.1.8" diff --git a/skills/inkbox-contact-lookup/SKILL.md b/skills/inkbox-contact-lookup/SKILL.md index 734622e..49002e8 100644 --- a/skills/inkbox-contact-lookup/SKILL.md +++ b/skills/inkbox-contact-lookup/SKILL.md @@ -1,11 +1,11 @@ --- name: inkbox-contact-lookup -description: Use when the user asks "who is X", "what's the email for Y", "find a contact named Z", "save this contact", or any request that needs contact context. The agent can read and write Inkbox contacts visible to the configured identity; contact access grants, contact rules, and vCard flows are separate surfaces. +description: Use when the user asks "who is X", "what's the email for Y", "find a contact named Z", "save this contact", or any request that needs contact context. Contacts are shared across the Inkbox organization; contact rules and vCard flows are separate surfaces. --- # Inkbox contact lookup -Inkbox contacts are an address-book scoped to the configured identity. When the user asks about a person, wants to save or edit a contact, or needs an address or number resolved, use the contact tools below. +Inkbox contacts are an organization-wide address book. When the user asks about a person, wants to save or edit a contact, or needs an address or number resolved, use the contact tools below. ## Required tools @@ -18,14 +18,14 @@ All six are enabled by default: - `inkbox_update_contact` — change an existing contact after you know its UUID - `inkbox_delete_contact` — delete a contact only after the target is explicit and confirmed -There is no vCard export/import tool. Contact access tools, contact rule tools, and `inkbox_place_call` are opt-in — the user must enable them in your .opencode/plugins/inkbox.ts wrapper and restart opencode: +There is no vCard export/import tool. Contact rule tools, note access tools, and `inkbox_place_call` are opt-in — the user must enable them in your .opencode/plugins/inkbox.ts wrapper and restart opencode: ```ts // in your .opencode/plugins/inkbox.ts wrapper: InkboxPlugin(input, { "tools": { "enable": ["inkbox_place_call", "access", "contact-rules"] } }) ``` -Use access and rule tools only when the user explicitly asks to manage sharing or allow/block rules. `inkbox_doctor` reports which tools are enabled or disabled. +Use note access and contact rule tools only when the user explicitly asks to manage note sharing or allow/block rules. `inkbox_doctor` reports which tools are enabled or disabled. ## Workflow @@ -38,9 +38,9 @@ Use access and rule tools only when the user explicitly asks to manage sharing o ## Access semantics -- Contact tools operate only on contacts visible/writable to the configured identity. +- Every identity in the organization can read contacts. Creating, updating, or deleting a contact affects the shared address book. - Contacts created through `inkbox_create_contact` are Inkbox address-book records, not local notes or session memory. -- Sharing contacts across Inkbox identities is grant management — that requires the opt-in access tools, and only when the user explicitly asks for it. +- Contacts do not have per-identity access grants. The opt-in access tools manage notes only. ## What this skill does NOT cover diff --git a/skills/inkbox-identity-access/SKILL.md b/skills/inkbox-identity-access/SKILL.md index 1445eea..4d4cf63 100644 --- a/skills/inkbox-identity-access/SKILL.md +++ b/skills/inkbox-identity-access/SKILL.md @@ -1,11 +1,11 @@ --- name: inkbox-identity-access -description: Use when the user asks which Inkbox agent identities can see a contact or note, or asks to grant/revoke cross-identity access to contacts or notes. +description: Use when the user asks which Inkbox agent identities can see a contact or note, or asks to grant/revoke cross-identity note access. Contacts are organization-wide; notes remain identity-scoped. --- # Inkbox identity access -Use this skill when managing per-identity visibility for Inkbox contacts and notes. +Use this skill when explaining organization-wide contact visibility or managing per-identity note access. ## Enabling the tools @@ -16,20 +16,16 @@ All identity-access tools are opt-in. Enable the `access` group in your .opencod InkboxPlugin(input, { "tools": { "enable": ["access"] } }) ``` -This enables `inkbox_list_contact_access`, `inkbox_grant_contact_access`, `inkbox_revoke_contact_access`, `inkbox_list_note_access`, `inkbox_grant_note_access`, and `inkbox_revoke_note_access`. Run `inkbox_doctor` to confirm which tools are currently enabled. +This enables `inkbox_list_note_access`, `inkbox_grant_note_access`, and `inkbox_revoke_note_access`. Run `inkbox_doctor` to confirm which tools are currently enabled. ## Workflow -1. Resolve the contact or note id first. If the user names a person or note, use `inkbox_lookup_contact`, `inkbox_list_contacts`, `inkbox_get_note`, or `inkbox_list_notes`. -2. List current access with `inkbox_list_contact_access` / `inkbox_list_note_access` before changing it when possible. -3. For contacts: - - Grant a specific identity with `identityId`. - - Use `wildcard: true` only when the user wants every active identity to see the contact; it replaces the specific grants. Pass either `identityId` or `wildcard`, never both. - - Revoke by `identityId`. -4. For notes: - - Grant and revoke only by explicit `identityId`; notes do not support wildcard grants. -5. If the user gives an agent handle instead of an identity UUID: `inkbox_whoami` returns the current identity's own id and handle, and access listings may already contain the other identity's id. If neither resolves the handle, explain that you need the identity id. +1. If the request concerns a contact, explain that every identity in the organization can see it and that contact access cannot be granted or revoked per identity. +2. For notes, resolve the note id with `inkbox_get_note` or `inkbox_list_notes`. +3. List current access with `inkbox_list_note_access` before changing it when possible. +4. Grant or revoke note access by explicit `identityId`; notes do not support wildcard grants. +5. If the user gives an agent handle instead of an identity UUID, `inkbox_whoami` returns the current identity's own id and handle, and note access listings may already contain the other identity's id. If neither resolves the handle, explain that you need the identity id. ## Safety -Access changes affect what other Inkbox agent identities can see. Confirm the target identity and object before granting broad or wildcard contact access. +Note access changes affect what other Inkbox agent identities can see. Confirm the target identity and note before changing access. diff --git a/src/gateway/contacts.ts b/src/gateway/contacts.ts index deeeb2d..9dc8b40 100644 --- a/src/gateway/contacts.ts +++ b/src/gateway/contacts.ts @@ -88,9 +88,8 @@ export function describeContacts(contacts: Contact[], max = 5): string { return lines.join("\n"); } -// Resolves inbound sender addresses to Inkbox contacts and derives the -// per-human session key. With an agent-scoped key, lookup results are -// already access-filtered server-side. +// Resolves inbound sender addresses to organization contacts and derives the +// per-human session key. export function createContactResolver( deps: { inkbox: InkboxRuntime; logger: GatewayLogger }, opts: { cacheTtlMs?: number } = {}, diff --git a/src/tools/access.ts b/src/tools/access.ts index 538da8e..2e8be53 100644 --- a/src/tools/access.ts +++ b/src/tools/access.ts @@ -3,24 +3,6 @@ import { runTool } from "../errors.js"; import { formatJson, formatWithHeader } from "../format.js"; import type { RegisteredTool, ToolDeps } from "./types.js"; -const listContactAccessArgs = { - contactId: z.string().describe("Contact UUID."), -}; - -const grantContactAccessArgs = { - contactId: z.string().describe("Contact UUID."), - identityId: z.string().describe("Agent identity UUID to grant.").optional(), - wildcard: z - .boolean() - .describe("Set true to replace specific grants with wildcard access.") - .optional(), -}; - -const revokeContactAccessArgs = { - contactId: z.string().describe("Contact UUID."), - identityId: z.string().describe("Agent identity UUID to revoke."), -}; - const listNoteAccessArgs = { noteId: z.string().describe("Note UUID."), }; @@ -35,89 +17,14 @@ const revokeNoteAccessArgs = { identityId: z.string().describe("Agent identity UUID to revoke."), }; -type ListContactAccessArgs = z.infer>; -type GrantContactAccessArgs = z.infer>; -type RevokeContactAccessArgs = z.infer>; type ListNoteAccessArgs = z.infer>; type GrantNoteAccessArgs = z.infer>; type RevokeNoteAccessArgs = z.infer>; -function hasString(value: unknown): value is string { - return typeof value === "string" && value.trim().length > 0; -} - -// Access grants control which identities can see a contact or a note. -// Contacts support wildcard grants (every active identity sees the contact); -// note grants are strictly per-identity. +// Notes remain identity-scoped even though contacts are organization-wide. export function accessTools(deps: ToolDeps): RegisteredTool[] { const { runtime } = deps; return [ - { - name: "inkbox_list_contact_access", - group: "access", - defaultEnabled: false, - definition: { - description: - "List which Inkbox identities can see a contact. Use before granting or revoking cross-identity contact access.", - args: listContactAccessArgs, - async execute(args: ListContactAccessArgs, _ctx) { - return runTool(async () => { - const inkbox = await runtime.getClient(); - const grants = await inkbox.contacts.access.list(args.contactId); - return formatWithHeader(`Returned ${grants.length} contact access grant(s).`, grants); - }); - }, - }, - }, - { - name: "inkbox_grant_contact_access", - group: "access", - defaultEnabled: false, - definition: { - description: - "Grant an Inkbox identity access to a contact, or grant wildcard access so every active identity can see it.", - args: grantContactAccessArgs, - async execute(args: GrantContactAccessArgs, _ctx) { - return runTool(async () => { - const identityId = hasString(args.identityId) ? args.identityId.trim() : undefined; - if (args.wildcard === true && identityId) { - throw new Error("Pass either identityId or wildcard=true, not both."); - } - if (args.wildcard !== true && !identityId) { - throw new Error("identityId is required unless wildcard=true."); - } - const inkbox = await runtime.getClient(); - const grant = await inkbox.contacts.access.grant(args.contactId, { - identityId, - wildcard: args.wildcard === true, - }); - return { - title: `Granted access to contact ${args.contactId}`, - output: formatWithHeader("Granted contact access.", grant), - }; - }); - }, - }, - }, - { - name: "inkbox_revoke_contact_access", - group: "access", - defaultEnabled: false, - definition: { - description: "Revoke one Inkbox identity's access to a contact.", - args: revokeContactAccessArgs, - async execute(args: RevokeContactAccessArgs, _ctx) { - return runTool(async () => { - const inkbox = await runtime.getClient(); - await inkbox.contacts.access.revoke(args.contactId, args.identityId); - return { - title: `Revoked access to contact ${args.contactId}`, - output: `Revoked identity ${args.identityId} access to contact ${args.contactId}.`, - }; - }); - }, - }, - }, { name: "inkbox_list_note_access", group: "access", diff --git a/src/tools/contacts.ts b/src/tools/contacts.ts index 54d1503..88e1723 100644 --- a/src/tools/contacts.ts +++ b/src/tools/contacts.ts @@ -128,10 +128,7 @@ function buildContactWritePayload(args: ContactWriteArgs): Record { @@ -174,8 +171,7 @@ export function contactTools(deps: ToolDeps): RegisteredTool[] { group: "contacts", defaultEnabled: true, definition: { - description: - "List contacts this identity has access to. Optional free-text search via `q`; results scoped by per-identity grants.", + description: "List organization-wide contacts. Optional free-text search via `q`.", args: listContactsArgs, async execute(args: ListContactsArgs, _ctx) { return runTool(async () => { @@ -197,7 +193,7 @@ export function contactTools(deps: ToolDeps): RegisteredTool[] { defaultEnabled: true, definition: { description: - "Create an Inkbox address-book contact. Use when the user asks to save a person/contact in Inkbox. Include phone/email when known; notes can hold free-form context.", + "Create an organization-wide Inkbox address-book contact. Use when the user asks to save a person/contact in Inkbox. Include phone/email when known; notes can hold free-form context.", args: createContactArgs, async execute(args: CreateContactArgs, _ctx) { return runTool(async () => { @@ -217,7 +213,7 @@ export function contactTools(deps: ToolDeps): RegisteredTool[] { defaultEnabled: true, definition: { description: - "Update an Inkbox address-book contact by UUID. Use after lookup/get when the user asks to add or correct contact details.", + "Update an organization-wide Inkbox address-book contact by UUID. Use after lookup/get when the user asks to add or correct contact details.", args: updateContactArgs, async execute(args: UpdateContactArgs, _ctx) { return runTool(async () => { @@ -237,7 +233,8 @@ export function contactTools(deps: ToolDeps): RegisteredTool[] { group: "contacts", defaultEnabled: true, definition: { - description: "Delete an Inkbox address-book contact by UUID. Irreversible.", + description: + "Delete an organization-wide Inkbox address-book contact by UUID. Irreversible.", args: deleteContactArgs, async execute(args: DeleteContactArgs, _ctx) { return runTool(async () => { diff --git a/tests/contract/tool-vocabulary.test.ts b/tests/contract/tool-vocabulary.test.ts index c209521..a36f88b 100644 --- a/tests/contract/tool-vocabulary.test.ts +++ b/tests/contract/tool-vocabulary.test.ts @@ -75,9 +75,6 @@ const OPT_IN = [ "inkbox_place_call", "inkbox_update_note", "inkbox_delete_note", - "inkbox_list_contact_access", - "inkbox_grant_contact_access", - "inkbox_revoke_contact_access", "inkbox_list_note_access", "inkbox_grant_note_access", "inkbox_revoke_note_access", @@ -111,10 +108,10 @@ const GROUPS = [ describe("tool vocabulary", () => { const all = buildAllTools(stubDeps()); - it("ships exactly the expected 51 tools", () => { + it("ships exactly the expected 48 tools", () => { const names = all.map((t) => t.name).sort(); expect(names).toEqual([...DEFAULT_ENABLED, ...OPT_IN].sort()); - expect(names).toHaveLength(51); + expect(names).toHaveLength(48); }); it("has no duplicate tool names", () => { diff --git a/tests/unit/access.test.ts b/tests/unit/access.test.ts index ff5075c..60e5ce4 100644 --- a/tests/unit/access.test.ts +++ b/tests/unit/access.test.ts @@ -30,20 +30,6 @@ function makeCtx() { function makeClient() { return { - contacts: { - access: { - list: vi.fn(async () => [ - { id: "grant-1", contactId: "contact-1", identityId: "identity-1", wildcard: false }, - ]), - grant: vi.fn(async () => ({ - id: "grant-2", - contactId: "contact-1", - identityId: "identity-2", - wildcard: false, - })), - revoke: vi.fn(async () => undefined), - }, - }, notes: { access: { list: vi.fn(async () => [ @@ -68,12 +54,9 @@ function outputText(result: unknown): string { } describe("accessTools", () => { - it("registers the six access tools in the access group", () => { + it("registers the three note access tools in the access group", () => { const tools = accessTools(makeDeps(makeClient())); expect(tools.map((t) => t.name)).toEqual([ - "inkbox_list_contact_access", - "inkbox_grant_contact_access", - "inkbox_revoke_contact_access", "inkbox_list_note_access", "inkbox_grant_note_access", "inkbox_revoke_note_access", @@ -91,130 +74,6 @@ describe("accessTools", () => { } }); - describe("inkbox_list_contact_access", () => { - it("lists grants for the contact and reports the count", async () => { - const client = makeClient(); - const tool = findTool(accessTools(makeDeps(client)), "inkbox_list_contact_access"); - const result = await tool.definition.execute({ contactId: "contact-1" }, makeCtx()); - expect(client.contacts.access.list).toHaveBeenCalledWith("contact-1"); - const text = outputText(result); - expect(text).toContain("Returned 1 contact access grant(s)."); - expect(text).toContain('"identityId": "identity-1"'); - }); - - it("declares a schema that requires contactId", () => { - const tool = findTool(accessTools(makeDeps(makeClient())), "inkbox_list_contact_access"); - const schema = z.object(tool.definition.args); - expect(schema.safeParse({ contactId: "contact-1" }).success).toBe(true); - expect(schema.safeParse({}).success).toBe(false); - expect(schema.safeParse({ contactId: 42 }).success).toBe(false); - }); - }); - - describe("inkbox_grant_contact_access", () => { - it("grants a specific identity access to the contact", async () => { - const client = makeClient(); - const tool = findTool(accessTools(makeDeps(client)), "inkbox_grant_contact_access"); - const result = await tool.definition.execute( - { contactId: "contact-1", identityId: "identity-2" }, - makeCtx(), - ); - expect(client.contacts.access.grant).toHaveBeenCalledWith("contact-1", { - identityId: "identity-2", - wildcard: false, - }); - const text = outputText(result); - expect(text).toContain("Granted contact access."); - expect(text).toContain('"identityId": "identity-2"'); - }); - - it("grants wildcard access without an identityId", async () => { - const client = makeClient(); - const tool = findTool(accessTools(makeDeps(client)), "inkbox_grant_contact_access"); - await tool.definition.execute({ contactId: "contact-1", wildcard: true }, makeCtx()); - expect(client.contacts.access.grant).toHaveBeenCalledWith("contact-1", { - identityId: undefined, - wildcard: true, - }); - }); - - it("trims surrounding whitespace from identityId before granting", async () => { - const client = makeClient(); - const tool = findTool(accessTools(makeDeps(client)), "inkbox_grant_contact_access"); - await tool.definition.execute( - { contactId: "contact-1", identityId: " identity-2 " }, - makeCtx(), - ); - expect(client.contacts.access.grant).toHaveBeenCalledWith("contact-1", { - identityId: "identity-2", - wildcard: false, - }); - }); - - it("rejects passing both identityId and wildcard=true", async () => { - const client = makeClient(); - const tool = findTool(accessTools(makeDeps(client)), "inkbox_grant_contact_access"); - await expect( - tool.definition.execute( - { contactId: "contact-1", identityId: "identity-2", wildcard: true }, - makeCtx(), - ), - ).rejects.toThrow("Pass either identityId or wildcard=true, not both."); - expect(client.contacts.access.grant).not.toHaveBeenCalled(); - }); - - it("rejects when neither identityId nor wildcard=true is given", async () => { - const client = makeClient(); - const tool = findTool(accessTools(makeDeps(client)), "inkbox_grant_contact_access"); - await expect(tool.definition.execute({ contactId: "contact-1" }, makeCtx())).rejects.toThrow( - "identityId is required unless wildcard=true.", - ); - expect(client.contacts.access.grant).not.toHaveBeenCalled(); - }); - - it("treats a whitespace-only identityId as missing", async () => { - const client = makeClient(); - const tool = findTool(accessTools(makeDeps(client)), "inkbox_grant_contact_access"); - await expect( - tool.definition.execute({ contactId: "contact-1", identityId: " " }, makeCtx()), - ).rejects.toThrow("identityId is required unless wildcard=true."); - expect(client.contacts.access.grant).not.toHaveBeenCalled(); - }); - - it("declares a schema with optional identityId and boolean wildcard", () => { - const tool = findTool(accessTools(makeDeps(makeClient())), "inkbox_grant_contact_access"); - const schema = z.object(tool.definition.args); - expect(schema.safeParse({ contactId: "c", identityId: "i" }).success).toBe(true); - expect(schema.safeParse({ contactId: "c", wildcard: true }).success).toBe(true); - expect(schema.safeParse({ contactId: "c" }).success).toBe(true); - expect(schema.safeParse({}).success).toBe(false); - expect(schema.safeParse({ contactId: "c", wildcard: "yes" }).success).toBe(false); - }); - }); - - describe("inkbox_revoke_contact_access", () => { - it("revokes the identity's access and summarizes it", async () => { - const client = makeClient(); - const tool = findTool(accessTools(makeDeps(client)), "inkbox_revoke_contact_access"); - const result = await tool.definition.execute( - { contactId: "contact-1", identityId: "identity-1" }, - makeCtx(), - ); - expect(client.contacts.access.revoke).toHaveBeenCalledWith("contact-1", "identity-1"); - expect(outputText(result)).toContain( - "Revoked identity identity-1 access to contact contact-1.", - ); - }); - - it("declares a schema that requires contactId and identityId", () => { - const tool = findTool(accessTools(makeDeps(makeClient())), "inkbox_revoke_contact_access"); - const schema = z.object(tool.definition.args); - expect(schema.safeParse({ contactId: "c", identityId: "i" }).success).toBe(true); - expect(schema.safeParse({ contactId: "c" }).success).toBe(false); - expect(schema.safeParse({ identityId: "i" }).success).toBe(false); - }); - }); - describe("inkbox_list_note_access", () => { it("lists grants for the note and reports the count", async () => { const client = makeClient(); From 11c6309d04da21b245e188ee1a25ac3e321684e1 Mon Sep 17 00:00:00 2001 From: alex-w-99 Date: Wed, 22 Jul 2026 19:33:13 +0000 Subject: [PATCH 2/2] Update SDK lockfile to 0.5.1 --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7859285..929f057 100644 --- a/package-lock.json +++ b/package-lock.json @@ -605,9 +605,9 @@ } }, "node_modules/@inkbox/sdk": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@inkbox/sdk/-/sdk-0.5.0.tgz", - "integrity": "sha512-F2jgPLzUSNH81MUa49HidpZuTNzBK+Ovk/7VfuV0ECOukYeDuVdApT6Sr2Yt8bug28T4BAc+yGyQZmiOh13ZzA==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@inkbox/sdk/-/sdk-0.5.1.tgz", + "integrity": "sha512-YQPCDsPesZLoscAk+XdreHgjnMco8yeN1ZjMOWXhAvNuX7JVUKttVS5GTer5QtIQ/dFRY+pp9cWjavKkL2AwSw==", "license": "MIT", "dependencies": { "@peculiar/x509": "^2.0.0",