Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/live-channels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ jobs:
python-version: "3.12"

- name: Install live-test deps (Inkbox SDK + pytest)
run: pip install 'inkbox>=0.5.0,<1.0.0' pytest
run: pip install 'inkbox>=0.5.1,<1.0.0' pytest

- name: Build the plugin
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/live-external-events.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ jobs:
python-version: "3.12"

- name: Install live-test deps (Inkbox SDK + pytest)
run: pip install 'inkbox>=0.5.0,<1.0.0' pytest
run: pip install 'inkbox>=0.5.1,<1.0.0' pytest

- name: Build the plugin
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/live-voice.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ jobs:
- name: Install live-test deps (Inkbox SDK + pytest + driver server)
# uvicorn[standard] (not bare uvicorn): the driver accepts a WebSocket
# upgrade, which needs the websockets extra.
run: pip install 'inkbox>=0.5.0,<1.0.0' pytest fastapi 'uvicorn[standard]'
run: pip install 'inkbox>=0.5.1,<1.0.0' pytest fastapi 'uvicorn[standard]'

- name: Build the plugin
run: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- uses: actions/checkout@v7
with:
repository: inkbox-ai/inkbox
ref: 8f4b37c56cd153a2acc6cfabe27dbbeb43510f00
ref: 199bbd27c8dab2f70e379de52ca9cc910b0e141d
path: .ci/inkbox

- uses: actions/setup-node@v7
Expand Down Expand Up @@ -60,7 +60,7 @@ jobs:
- uses: actions/checkout@v7
with:
repository: inkbox-ai/inkbox
ref: 8f4b37c56cd153a2acc6cfabe27dbbeb43510f00
ref: 199bbd27c8dab2f70e379de52ca9cc910b0e141d
path: .ci/inkbox

- uses: actions/setup-node@v7
Expand Down
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ All notable changes to the Inkbox OpenClaw plugin are listed here. The format fo

### Changed

- Raised the Inkbox SDK dependency floor to 0.5.0 for the plugin and live CI.
- Raised the Inkbox SDK dependency floor to 0.5.1 for the plugin and live CI.
- Removed contact access tools because contacts are organization-wide. Note access tools remain identity-scoped.

## [0.2.2] - 2026-07-10

Expand Down
9 changes: 3 additions & 6 deletions PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,9 +336,9 @@ OpenClaw skills are markdown files that scope agent behavior for a domain. Ship
| `inkbox-sms-responder` | "text X", inbound `text.received` event | Conversational SMS reply, conversation-history-aware |
| `inkbox-outbound-calling` | "call X", "place a call to Y" | Place calls from the configured Inkbox phone number |
| `inkbox-call-review` | "what happened on the call", "show transcripts" | Review call history and transcript segments |
| `inkbox-contact-lookup` | "who is X", "find email for Y" | Lookup-first; surfaces vcard + notes if access-granted |
| `inkbox-contact-lookup` | "who is X", "find email for Y" | Lookup-first access to the shared organization address book |
| `inkbox-contact-rules` | "block/allow this sender/number" | Manage mailbox and phone contact rules |
| `inkbox-identity-access` | "share this contact/note with identity X" | Manage contact/note access grants |
| `inkbox-identity-access` | "share this note with identity X" | Explain shared contact visibility and manage note access grants |
| `inkbox-credential-use` | "log into X", "I need the TOTP for Y" | Gates plaintext credential access with explicit confirmation |
| `inkbox-outreach-sequence` | "follow up with X over 3 days" | Multi-step outbound (email + SMS) with delay scheduling |

Expand Down Expand Up @@ -456,7 +456,7 @@ Grouped by phase. ✱ = optional (user must opt-in via `tools: { allow: [...] }`
**Phase 7 — Diagnostics**
- `inkbox_whoami` ✱

**Contact rules + access grants**
**Contact rules + note access grants**
- `inkbox_list_mail_contact_rules` ✱
- `inkbox_create_mail_contact_rule` ✱
- `inkbox_update_mail_contact_rule` ✱
Expand All @@ -465,9 +465,6 @@ Grouped by phase. ✱ = optional (user must opt-in via `tools: { allow: [...] }`
- `inkbox_create_phone_contact_rule` ✱
- `inkbox_update_phone_contact_rule` ✱
- `inkbox_delete_phone_contact_rule` ✱
- `inkbox_list_contact_access` ✱
- `inkbox_grant_contact_access` ✱
- `inkbox_revoke_contact_access` ✱
- `inkbox_list_note_access` ✱
- `inkbox_grant_note_access` ✱
- `inkbox_revoke_note_access` ✱
Expand Down
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,6 @@ openclaw config set tools.allow '[
"inkbox_create_phone_contact_rule",
"inkbox_update_phone_contact_rule",
"inkbox_delete_phone_contact_rule",
"inkbox_list_contact_access",
"inkbox_grant_contact_access",
"inkbox_revoke_contact_access",
"inkbox_list_note_access",
"inkbox_grant_note_access",
"inkbox_revoke_note_access",
Expand Down Expand Up @@ -335,7 +332,7 @@ Optional:
- iMessage: `inkbox_imessage_triage_number`, `inkbox_list_imessage_assignments`, `inkbox_send_imessage_reaction`
- Notes: `inkbox_update_note`, `inkbox_delete_note`
- Contact rules: `inkbox_list_mail_contact_rules`, `inkbox_create_mail_contact_rule`, `inkbox_update_mail_contact_rule`, `inkbox_delete_mail_contact_rule`, `inkbox_list_phone_contact_rules`, `inkbox_create_phone_contact_rule`, `inkbox_update_phone_contact_rule`, `inkbox_delete_phone_contact_rule`
- Identity 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`
- Note access: `inkbox_list_note_access`, `inkbox_grant_note_access`, `inkbox_revoke_note_access`
- Vault: `inkbox_credentials_list`, `inkbox_credentials_get_login`, `inkbox_credentials_get_api_key`, `inkbox_credentials_get_ssh_key`, `inkbox_totp_code`
- Diagnostic: `inkbox_whoami`

Expand Down
6 changes: 0 additions & 6 deletions openclaw.plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@
"inkbox_create_phone_contact_rule",
"inkbox_update_phone_contact_rule",
"inkbox_delete_phone_contact_rule",
"inkbox_list_contact_access",
"inkbox_grant_contact_access",
"inkbox_revoke_contact_access",
"inkbox_list_note_access",
"inkbox_grant_note_access",
"inkbox_revoke_note_access",
Expand Down Expand Up @@ -279,9 +276,6 @@
"inkbox_create_phone_contact_rule": { "optional": true },
"inkbox_update_phone_contact_rule": { "optional": true },
"inkbox_delete_phone_contact_rule": { "optional": true },
"inkbox_list_contact_access": { "optional": true },
"inkbox_grant_contact_access": { "optional": true },
"inkbox_revoke_contact_access": { "optional": true },
"inkbox_list_note_access": { "optional": true },
"inkbox_grant_note_access": { "optional": true },
"inkbox_revoke_note_access": { "optional": true },
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"test:watch": "vitest"
},
"dependencies": {
"@inkbox/sdk": ">=0.5.0 <1.0.0",
"@inkbox/sdk": ">=0.5.1 <1.0.0",
"json5": "^2.2.3",
"qrcode-terminal": "^0.12.0",
"typebox": "^1.1.38"
Expand Down
10 changes: 5 additions & 5 deletions skills/inkbox-contact-lookup/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
---
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 question that needs contact context. OpenClaw can read and write Inkbox contacts visible to this identity, but contact access/rules and vCard flows are separate admin 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 question that needs contact context. Contacts are shared across the Inkbox organization; contact rules and vCard flows are separate surfaces.
user-invocable: false
---

# Inkbox contact lookup

OpenClaw is the Inkbox personal-assistant tier. It receives contact context on inbound email, SMS, iMessage, and calls when Inkbox resolves the sender, and it can read or update contacts visible to the configured identity.
OpenClaw receives contact context on inbound email, SMS, iMessage, and calls when Inkbox resolves the sender, and it can read or update the organization's shared contacts.

## Required tools

Expand All @@ -17,7 +17,7 @@ OpenClaw is the Inkbox personal-assistant tier. It receives contact context on i
- `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 in this harness. Contact access and contact rule tools are separate admin tools; use those only when the user explicitly asks to manage sharing or allow/block rules.
There is no vCard export/import tool in this harness. Contact rule and note access tools are separate tools; use those only when the user explicitly asks to manage allow/block rules or note sharing.

## Workflow

Expand All @@ -31,9 +31,9 @@ There is no vCard export/import tool in this harness. Contact access and contact

## 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 workspace memories.
- Grant management is handled by the `inkbox-identity-access` skill when the user asks to share contacts across Inkbox identities.
- Contacts do not have per-identity access grants. The `inkbox-identity-access` skill manages notes only.

## What this skill does NOT cover

Expand Down
23 changes: 8 additions & 15 deletions skills/inkbox-identity-access/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,27 @@
---
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.
user-invocable: false
---

# 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.

## Optional tools

- `inkbox_list_contact_access`
- `inkbox_grant_contact_access`
- `inkbox_revoke_contact_access`
- `inkbox_list_note_access`
- `inkbox_grant_note_access`
- `inkbox_revoke_note_access`

## Workflow

1. Resolve the contact or note id first. Use lookup/list/get tools if the user names a person or note.
2. List current 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.
- 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 and no tool can resolve handles, explain that you need the identity id or a contact/note access listing that contains it.
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 the note lookup/list/get tools.
3. List current note access before changing it when possible.
4. Grant and revoke note access only by explicit `identityId`; notes do not support wildcard grants.
5. If the user gives an agent handle instead of an identity UUID and no tool can resolve handles, explain that you need the identity id or a note access listing that contains it.

## 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.
79 changes: 1 addition & 78 deletions src/tools/access.ts
Original file line number Diff line number Diff line change
@@ -1,86 +1,9 @@
import { Type } from "typebox";
import type { InkboxRuntime } from "../client.js";
import { runTool, toolError, toolText } from "../errors.js";
import { runTool, toolText } from "../errors.js";
import { formatJson, formatWithHeader } from "../format.js";

function hasString(value: unknown): value is string {
return typeof value === "string" && value.trim().length > 0;
}

export function registerIdentityAccessTools(api: any, runtime: InkboxRuntime): void {
api.registerTool(
{
name: "inkbox_list_contact_access",
description:
"List which Inkbox identities can see a contact. Use before granting or revoking cross-identity contact access.",
parameters: Type.Object({
contactId: Type.String({ description: "Contact UUID." }),
}),
async execute(_id: string, params: any) {
return runTool(async () => {
const inkbox = await runtime.getClient();
const grants = await inkbox.contacts.access.list(params.contactId);
return toolText(formatWithHeader(`Returned ${grants.length} contact access grant(s).`, grants));
});
},
},
{ optional: true },
);

api.registerTool(
{
name: "inkbox_grant_contact_access",
description:
"Grant an Inkbox identity access to a contact, or grant wildcard access so every active identity can see it.",
parameters: Type.Object({
contactId: Type.String({ description: "Contact UUID." }),
identityId: Type.Optional(Type.String({ description: "Agent identity UUID to grant." })),
wildcard: Type.Optional(
Type.Boolean({
description: "Set true to replace specific grants with wildcard access.",
}),
),
}),
async execute(_id: string, params: any) {
return runTool(async () => {
const identityId = hasString(params.identityId) ? params.identityId.trim() : undefined;
if (params.wildcard === true && identityId) {
return toolError("Pass either identityId or wildcard=true, not both.");
}
if (params.wildcard !== true && !identityId) {
return toolError("identityId is required unless wildcard=true.");
}
const inkbox = await runtime.getClient();
const grant = await inkbox.contacts.access.grant(params.contactId, {
identityId,
wildcard: params.wildcard === true,
});
return toolText(formatWithHeader("Granted contact access.", grant));
});
},
},
{ optional: true },
);

api.registerTool(
{
name: "inkbox_revoke_contact_access",
description: "Revoke one Inkbox identity's access to a contact.",
parameters: Type.Object({
contactId: Type.String({ description: "Contact UUID." }),
identityId: Type.String({ description: "Agent identity UUID to revoke." }),
}),
async execute(_id: string, params: any) {
return runTool(async () => {
const inkbox = await runtime.getClient();
await inkbox.contacts.access.revoke(params.contactId, params.identityId);
return toolText(`Revoked identity ${params.identityId} access to contact ${params.contactId}.`);
});
},
},
{ optional: true },
);

api.registerTool(
{
name: "inkbox_list_note_access",
Expand Down
15 changes: 6 additions & 9 deletions src/tools/contacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,12 @@ function buildContactWritePayload(params: any) {
return payload;
}

// Contacts are an org-level address book filtered server-side by per-identity
// access grants. With an agent-scoped key, list/lookup/get already return only
// the contacts this identity has access to — we don't filter client-side.
// Grant management stays admin-only and is not exposed.
// Contacts are shared across every identity in the organization.
export function registerContactTools(api: any, runtime: InkboxRuntime): void {
api.registerTool({
name: "inkbox_lookup_contact",
description:
"Reverse-lookup contacts by email or phone. Exactly one filter must be provided — email, phone, emailDomain, emailContains, or phoneContains. Returns contacts this identity has access to.",
"Reverse-lookup organization contacts by email or phone. Exactly one filter must be provided — email, phone, emailDomain, emailContains, or phoneContains.",
parameters: Type.Object({
email: Type.Optional(Type.String({ description: "Exact email address." })),
phone: Type.Optional(Type.String({ description: "Exact E.164 phone number." })),
Expand Down Expand Up @@ -106,7 +103,7 @@ export function registerContactTools(api: any, runtime: InkboxRuntime): void {
api.registerTool({
name: "inkbox_list_contacts",
description:
"List contacts this identity has access to. Optional free-text search via `q`; results scoped by per-identity grants.",
"List organization-wide contacts. Optional free-text search via `q`.",
parameters: Type.Object({
q: Type.Optional(
Type.String({
Expand Down Expand Up @@ -143,7 +140,7 @@ export function registerContactTools(api: any, runtime: InkboxRuntime): void {
api.registerTool({
name: "inkbox_create_contact",
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.",
parameters: Type.Object({
preferredName: Type.Optional(Type.String({ description: "Display/preferred name." })),
givenName: Type.Optional(Type.String({ description: "Given/first name." })),
Expand All @@ -166,7 +163,7 @@ export function registerContactTools(api: any, runtime: InkboxRuntime): void {
api.registerTool({
name: "inkbox_update_contact",
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.",
parameters: Type.Object({
contactId: Type.String({ description: "UUID of the contact to update." }),
preferredName: Type.Optional(Type.Union([Type.String(), Type.Null()])),
Expand All @@ -192,7 +189,7 @@ export function registerContactTools(api: any, runtime: InkboxRuntime): void {

api.registerTool({
name: "inkbox_delete_contact",
description: "Delete an Inkbox address-book contact by UUID. Irreversible.",
description: "Delete an organization-wide Inkbox address-book contact by UUID. Irreversible.",
parameters: Type.Object({
contactId: Type.String({ description: "UUID of the contact to delete." }),
}),
Expand Down
Loading