@hraness/message-like-me 0.8.11 → 0.8.13

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.
@@ -72,10 +72,12 @@ existing GitHub Apps, rulesets, protected refs, status contexts, environment
72
72
  reviewers, workflow IDs, Vercel project, and production branch unchanged. The
73
73
  canonical source identity for new release and promotion runs is
74
74
  `hraness/textbutler`; an old-name redirect is not authority for a new run.
75
- The npm names `@hraness/message-like-me` and `@hraness/agentrouter`, their tag
76
- namespaces, the `messagelikeme` command, wire schemas, and historical release
77
- receipts remain unchanged. Do not rewrite an existing tag, npm version, or
78
- provenance statement.
75
+ The npm name `@hraness/message-like-me`, its tag namespace, the
76
+ `messagelikeme` command, wire schemas, and historical release receipts remain
77
+ unchanged. `@hraness/agentmixer` publishes independently from the
78
+ `hraness/agentmixer` repository under its own `v*` tag namespace; this
79
+ repository consumes it only as a pinned immutable release artifact. Do not
80
+ rewrite an existing tag, npm version, or provenance statement.
79
81
 
80
82
  Merge this version-neutral control migration independently before the next
81
83
  product/version change. Refresh the complete administrative controls census
@@ -114,32 +116,15 @@ denial, expected-old lease, and provider readback are machine gates. An agent
114
116
  may perform the independent review and exact dispatch required for a changed
115
117
  workflow-control epoch; that review precedes dispatch.
116
118
 
117
- ## AgentRouter package publication
118
-
119
- `@hraness/agentrouter` publishes through its own tag namespace and its own
120
- tag-triggered workflow, `release-agentrouter.yml`, mirroring the root Release
121
- contract. An annotated `agentrouter-v<version>` tag on a reviewed `main`
122
- ancestor runs the same verify → exact-artifact → immutable GitHub Release →
123
- read-only retry admission → OIDC-only npm writer → final public admission
124
- chain, with the AgentRouter package manifest, tarball name, tag prefix, and
125
- workflow path bound through the shared closed release-package descriptor. Both
126
- release workflows share concurrency group `stable-release`, so a root and an
127
- AgentRouter release can never interleave their GitHub Latest assertions. The
128
- AgentRouter channel has no site or production-promotion coupling; its releases
129
- distribute the package only.
130
-
131
- Owner-side controls required once before the first AgentRouter tag:
132
-
133
- - Extend the immutable-tag ruleset coverage to `refs/tags/agentrouter-v*` with
134
- the same update and deletion restrictions and no bypass actors as the
135
- existing `refs/tags/v*` scope.
136
- - Ensure `@hraness/agentrouter` exists publicly under the Hraness npm scope,
137
- then configure its sole trusted publisher as GitHub Actions repository
138
- `hraness/textbutler`, workflow file `release-agentrouter.yml`. Require
139
- the exact permission set `createPackage` plus npm's provider-imposed
140
- `createStagedPackage`, and keep the staged-package inventory exactly empty.
141
- Once trusted publishing is proven, disallow traditional token publication
142
- for the package.
119
+ ## AgentMixer package consumption
120
+
121
+ `@hraness/agentmixer` is published independently from the `hraness/agentmixer`
122
+ repository; its tag namespace, release workflow, provenance identity, and npm
123
+ trusted publisher live there and are governed by that repository's runbook.
124
+ This repository consumes it only as a pinned, immutable GitHub Release
125
+ tarball. Upgrading the pin is a reviewed `package.json`/`bun.lock` change
126
+ against an already-admitted upstream release; it never re-runs, rewrites, or
127
+ co-signs an upstream release.
143
128
 
144
129
  ## Establish the production controls once
145
130
 
@@ -892,8 +877,14 @@ admission both succeeded, so a skipped tail cannot make the workflow green.
892
877
  If the ref is already exact, the baseline marks advancement false, skips the
893
878
  entire `production-ref-writer-key` job, and mints no App token. A separate
894
879
  read-only job accepts only the unique latest exact-SHA Production deployment in
895
- the stable baseline that postdates the immutable Release. That newest attempt
896
- itself must be provider-accepted. A newer terminal failure, error, or inactive
880
+ the stable baseline that postdates the immutable Release, or, when the
881
+ separately admitted site route already advanced the ref to that exact commit
882
+ before the Release was published, that postdates the status App's admitted
883
+ `success` of the consumed site authority on that commit. That consumed
884
+ authority must already carry the App's terminal `error`; any other authority
885
+ shape, actor, or ordering keeps the Release publication as the boundary, so
886
+ this route can only admit a deployment that an admitted site promotion
887
+ created. That newest attempt itself must be provider-accepted. A newer terminal failure, error, or inactive
897
888
  attempt blocks recovery instead of allowing an older success to be reused.
898
889
  Recovery then repeats the terminal authority readbacks. A missing ref is a hard
899
890
  failure and must not be recreated by the workflow. If the desired transition
@@ -0,0 +1,123 @@
1
+ # Use TextButler from an agent
2
+
3
+ TextButler's CLI returns JSON for conversation reads, summaries, drafts and
4
+ sends. Run it as the signed-in Mac user with the TextButler daemon running.
5
+ Connect iMessage and your AI subscription using the [setup guide](getting-started.md).
6
+ Explicit owner commands work while automatic replies are paused and the
7
+ selected contact is disabled.
8
+
9
+ ## Select a conversation
10
+
11
+ ```sh
12
+ textbutler conversations list
13
+ textbutler contacts add CANDIDATE_ID
14
+ textbutler contacts list
15
+ textbutler messages capabilities CONTACT_ID
16
+ ```
17
+
18
+ Use the exact IDs returned by the preceding commands. A unique contact name
19
+ also works; ambiguous names fail. Adding a contact keeps automatic replies off.
20
+ Add `--history` to `contacts add` only when you want a retained history import.
21
+ Reading current history does not require that import.
22
+
23
+ ## Read and summarize
24
+
25
+ ```sh
26
+ textbutler messages history CONTACT_ID --limit 100
27
+ textbutler contacts account CONTACT_ID native-claude-code
28
+ textbutler messages summarize CONTACT_ID --limit 100
29
+ ```
30
+
31
+ History includes message IDs, authorship, time, text, related message IDs and
32
+ attachment metadata when the provider exposes it. Attachment metadata contains
33
+ names, media types and sizes; local file paths and contents are excluded.
34
+ Limits range from 1 to 200 messages. The response reports
35
+ shortened text and omitted records; it is a recent sample, not a complete
36
+ archive. It does not download or interpret attachment contents.
37
+
38
+ Summaries use the contact's selected, qualified subscription account. The
39
+ response includes source message IDs and the size of the sample used. Review
40
+ the generated interpretation against those messages. A calling agent can also
41
+ summarize the history JSON itself without starting another inference request.
42
+
43
+ ## Compose, review and send
44
+
45
+ Choose one way to create an unsent draft. For an AI suggestion:
46
+
47
+ ```sh
48
+ textbutler replies suggest CONTACT_ID
49
+ ```
50
+
51
+ Suggestions read the current conversation without requiring a history import.
52
+ If there is no unanswered incoming message, the command returns no draft.
53
+ To provide the content yourself instead:
54
+
55
+ ```sh
56
+ textbutler messages compose CONTACT_ID --text 'Tuesday works for me.'
57
+ ```
58
+
59
+ Use the returned draft ID to review it, then send only when instructed:
60
+
61
+ ```sh
62
+ textbutler replies show DRAFT_ID
63
+ textbutler replies send DRAFT_ID DIGEST
64
+ ```
65
+
66
+ Use the digest returned by the complete draft review. It binds the recipient,
67
+ content and imported media. Changed conversations or media can invalidate a
68
+ draft. Disclosure settings apply to the reviewed and sent content.
69
+ There is one active draft per contact; creating another replaces it. Drafts
70
+ expire after 15 minutes and are cleared when the daemon restarts. Use
71
+ `textbutler replies discard DRAFT_ID` to discard one explicitly.
72
+
73
+ An explicitly authorized literal text can be sent in one command:
74
+
75
+ ```sh
76
+ textbutler messages send CONTACT_ID --text 'I have arrived.'
77
+ ```
78
+
79
+ Treat that command as an outward action. A CLI's availability is not permission
80
+ for an agent to message someone without its user's instruction.
81
+
82
+ ## Media and reactions
83
+
84
+ Inspect `messages capabilities CONTACT_ID` before requesting an action.
85
+ These commands create unsent drafts for the same review and send flow:
86
+
87
+ ```sh
88
+ textbutler messages attach CONTACT_ID /absolute/photo.jpg --caption 'Our view today'
89
+ textbutler messages react CONTACT_ID MESSAGE_ID '👍'
90
+ textbutler messages react CONTACT_ID MESSAGE_ID '👍' --remove
91
+ ```
92
+
93
+ Media imports accept owned regular files up to 16 MiB and copy them into the
94
+ selected contact's private outbox. The draft records the imported bytes; later
95
+ changes to the original source file do not change that copy. Common image,
96
+ audio, video and document types are recognized. Transport support and current
97
+ account permissions still determine whether an attachment can be sent.
98
+
99
+ For an ordered batch, put 1–7 supported action objects in a JSON array and use
100
+ `messages compose CONTACT_ID --actions /absolute/actions.json`. The supported
101
+ shapes are defined by the [action types](../../packages/transport/src/types.ts).
102
+ Attachment and sticker paths in an action object are relative to that contact's
103
+ workspace. The daemon validates targets, capabilities and imported media.
104
+
105
+ The current connector supports ordinary text and media on a Mac with the
106
+ required permissions. Standard reactions, stickers, rich links and polls
107
+ require its separately configured Messages bridge and the corresponding
108
+ advertised capability. TextButler does not install that bridge or change macOS
109
+ security settings. Outgoing threaded reply targeting, App Clips and experiences
110
+ are unsupported by the iMessage connector. The CLI does not turn a requested
111
+ thread reply into an ordinary message. Incoming reply relationships remain
112
+ visible in history.
113
+
114
+ ## Read uncertain results
115
+
116
+ Long operations may return a job ID. Read that exact job with
117
+ `textbutler jobs show JOB_ID`; do not repeat the original send. A successful
118
+ transport receipt reports `submitted`. A `partial` or `indeterminate` outcome
119
+ needs reconciliation before another attempt. Commands return a nonzero exit
120
+ code for pending or unsuccessful sends.
121
+
122
+ Append `--data-dir /absolute/private/path` to use another installation.
123
+ `textbutler messages --help` lists the agent commands.
@@ -1,6 +1,6 @@
1
1
  # Textbutler architecture
2
2
 
3
- Textbutler is a personal message butler for macOS. An owner activates a bounded set of contacts. Each contact gets a private workspace that a coding agent can read and evolve. A separate daemon decides when to invoke that agent and controls every outward action.
3
+ Textbutler is a personal message butler for macOS. An owner activates a bounded set of contacts. Each contact gets a private workspace that a model can read and evolve through Textbutler's broker. A separate daemon decides when to invoke that agent and controls every outward action.
4
4
 
5
5
  The source includes the owner daemon, contact reply loop, versioned Ghostget automation protocol and macOS menu companion. Synthetic tests establish their control and recovery behavior. Live provider delivery has separate acceptance requirements below; the companion is a CLI artifact and has no signing or notarization gate.
6
6
 
@@ -10,9 +10,11 @@ The source includes the owner daemon, contact reply loop, versioned Ghostget aut
10
10
  flowchart LR
11
11
  Menu[Menu companion] --> Control[Owner-only control socket]
12
12
  Control --> Butler[Textbutler daemon]
13
- Butler --> Router[Agentrouter]
14
- Router --> Provider[Admitted agent provider]
15
- Provider --> Tools[Contact-bound tool broker]
13
+ Butler --> Xcb[xcb zero-tool generate]
14
+ Xcb --> Provider[Admitted subscription provider]
15
+ Provider --> Proposal[Structured result or proposal]
16
+ Proposal --> Butler
17
+ Butler --> Tools[Contact-bound tool broker]
16
18
  Tools --> Memory[One contact workspace]
17
19
  Tools --> Web[Public web broker]
18
20
  Tools --> Intents[Proposed actions]
@@ -22,9 +24,9 @@ flowchart LR
22
24
  Ghostget --> Messages[iMessage and WhatsApp]
23
25
  ```
24
26
 
25
- Ghostget owns native permissions, message and contact acquisition, provider actions, and receipts. Textbutler does not open chat.db or automate Messages directly. Agentrouter owns provider selection, shared-account leases, cancellation, model catalogs, qualification evidence, and a bounded tool interface. Textbutler owns conversation policy and the durable send transaction. The menu companion changes owner settings through a small local protocol; it never gives a model arbitrary local commands.
27
+ Ghostget owns native permissions, message and contact acquisition, provider actions, and receipts. Textbutler does not open chat.db or automate Messages directly. xcb owns subscription credentials, provider admission, model catalogs, confinement, cancellation and account custody. Textbutler invokes its native zero-tool generation API and interprets returned operation proposals through its own contact broker. Textbutler owns conversation policy and the durable send transaction. The menu companion changes owner settings through a small local protocol; it never gives a model arbitrary local commands.
26
28
 
27
- The background lifecycle uses a user LaunchAgent: native messaging belongs to the signed-in Mac user, and login persistence is independent of the menu companion. Installation records the exact runtime, entrypoint, data directory and generation; removal verifies its private receipt and loaded service identity. Uninstall preserves contact data. New settings start paused. A private SQLite custody lock prevents duplicate daemon ownership and permits recovery only for a dead recorded process and its exact unserved socket. A temporary live launchd install/start/uninstall test passed while preserving synthetic owner data. The CLI companion is the supported release path; desktop app packaging has been removed; the CLI companion is the only local runtime surface.
29
+ The background lifecycle uses a user LaunchAgent: native messaging belongs to the signed-in Mac user, and login persistence is independent of the menu companion. Installation records the exact runtime, entrypoint, data directory and generation; removal verifies its private receipt and loaded service identity. Uninstall preserves contact data. New settings start paused. A private SQLite custody lock prevents duplicate daemon ownership and permits recovery only for a dead recorded process and its exact unserved socket. A minimal native TextButler.app can supervise the verified daemon so Full Disk Access belongs to the app. Its private installation receipt pins the runtime, payload, native executable and signature resources; a changed identity blocks startup. The app accepts fixed daemon, menu and iMessage setup roles, with no arbitrary command passthrough. Local builds use an ad-hoc signature, so a rebuilt app may require a fresh macOS permission grant.
28
30
 
29
31
  The daemon serves owner controls, enrollment jobs and a reply loop through its own supervised Ghostget process. Startup, enablement and recovery establish a silent event boundary, refresh bounded history and admit only subsequent eligible inbound messages. A failed catch-up pauses the affected conversation. No provider work starts without explicit owner account configuration.
30
32
 
@@ -37,7 +39,7 @@ Textbutler/
37
39
  daemon.sock owner-only native control socket
38
40
  state/
39
41
  settings.json owner configuration and contact bindings
40
- host.json optional private installed Ghostget CLI/account binding
42
+ host.json private Ghostget and xcb executable/account bindings
41
43
  runs.sqlite private run and send journal
42
44
  daemon-custody.sqlite exclusive process/socket ownership
43
45
  launch-agent-custody.sqlite lifecycle serialization
@@ -74,7 +76,7 @@ The daemon waits eight seconds after an incoming message to collect a burst. New
74
76
 
75
77
  A whole-word, case-insensitive `butler` invocation permits a response after those deterministic gates. In keyword mode, other messages stay silent. In smart mode, a tool-free cheap model returns a strict structured classification. It should answer useful assistance requests, and stay silent during ordinary conversation, acknowledgments, emotional exchanges, or uncertain intent. Confidence below 0.85 stays silent. Malformed results, exhausted accounts, stale model catalogs, or timeouts never escalate to a more expensive agent automatically.
76
78
 
77
- Classifier choice comes from the selected provider's fresh available-model catalog, with explicit cost metadata and structured-output capability. No permanently hard-coded "cheap" model alias is assumed. An owner can pin a classifier or reply model after availability validation. Classifier and responder share the chosen provider/account policy; one classifier gets no tool authority.
79
+ The xcb subscription route uses the owner-selected full model key for classification and replies; a subscription model is not assigned invented API prices. The separately billed API route selects its classifier from fresh availability and explicit cost metadata. Classifier and responder share the chosen provider/account policy, and classification receives no operation authority.
78
80
 
79
81
  No typing signal can prove the owner is absent. The current adapters expose no typing signal; the daemon retains its message-based cooldown and final revision checks. The selected provider/account must independently qualify before smart mode can invoke a model.
80
82
 
@@ -82,35 +84,80 @@ No typing signal can prove the owner is absent. The current adapters expose no t
82
84
 
83
85
  The model proposes actions. It does not dispatch them. Owner-installed hooks may shape the work or veto a reply, but all action validation, contact binding, limits, and disclosure run afterwards.
84
86
 
85
- Every text action is wrapped by trusted code with the contact's three symbols. Each field must be one visible grapheme; an empty or invisible disclosure is rejected. Default rendering is `🤖{ hello this is my response }`.
87
+ Every text action is wrapped by trusted code with the contact's three symbols. Each field is either cleared (empty) or exactly one visible grapheme; invisible or multi-grapheme fields are rejected. Default rendering is `🤖{ hello this is my response }`. Clearing all three fields removes the visible wrap entirely and sends plain text.
86
88
 
87
- Reactions, stickers, link previews, and app cards cannot literally carry that text prefix. A disclosed text companion is therefore the first action in a nontext response, and counts toward the eight-action maximum. The provider must execute in order and stop if that companion fails. App-specific cards may additionally identify Textbutler in their content, but never remove the companion requirement.
89
+ Clearing disclosure never makes butler output indistinguishable internally. The transport reports the provider-accepted message IDs for every submitted action, and the daemon records them in a private `sent_messages` journal table. History and attribution classify an outgoing message as butler-authored through that journal first, and through the configured visible wrap for sends that predate it. A cleared wrap simply has no visible form; provenance stays exact.
90
+
91
+ Reactions, stickers, link previews, and app cards cannot literally carry that text prefix. When visible markers remain configured, a disclosed text companion is therefore the first action in a nontext response, and counts toward the eight-action maximum. With disclosure fully cleared the companion would be unexplained extra text and is not added. The provider must execute in order and stop if that companion fails. App-specific cards may additionally identify Textbutler in their content, but never remove the companion requirement.
88
92
 
89
93
  Before send, the runtime rechecks owner activity, conversation revision, current settings, capability availability, cancellation, attachment ownership, target message membership, and the contact grant. It asks the transport to prepare an exact plan with an expiry and digest. Immediately before submission, it journals the dispatch intent. The transport must atomically validate the grant, contact route, context revision, and plan digest at its own effect boundary.
90
94
 
91
95
  `submitted` is not `delivered`. Partial and indeterminate outcomes pause further automated activity for that contact until explicit reconciliation. A crash while dispatching becomes indeterminate on recovery; it never causes a blind retry. A crash before dispatch abandons the run without sending. Provider failover cannot replay a possibly submitted action.
92
96
 
93
- ## Hooks and plugins
97
+ ## Owner reply triage
94
98
 
95
- The initial lifecycle is `message.received`, `reply.decide`, `reply.compose`, `reply.before-send`, `reply.sent`, `memory.updated`, and `run.failed`. Hooks have a named/versioned owner-installed extension, deterministic registration order, a deadline, and a cancellation signal. Failure before dispatch closes admission. A notification-hook failure after a receipt cannot change that receipt or trigger resend.
99
+ The same machinery serves an explicit owner workflow that is distinct from automatic replies. `textbutler inbox` (or the menu's **Replies → Check for replies**) runs a bounded read-only pass over every enrolled conversation and reports each trailing run of unanswered inbound messages: the contact, a bounded sanitized preview, the pending count, whether a send is currently possible, and why not when it is not. The automatic loop's live observations feed the same view, so the inbox reflects what the daemon already saw between scans.
96
100
 
97
- Executable extensions are application code with the daemon's trust. They are installed outside contact folders; the agent cannot write them or turn message text into imports. Agent self-evolution means revising guidance and memory, not installing executable code. A future untrusted plugin mode needs its own process or language sandbox and explicit capabilities. In-process hooks are never described as a plugin security boundary.
101
+ `textbutler replies suggest CONTACT` asks the contact's configured agent to draft a reply for that pending run. A suggestion is a bounded draft with a fifteen-minute expiry: summary, exact proposed actions, and the disclosed preview the send would carry. It never dispatches. Drafts bind the conversation revision and disclosure settings they were created against; a stale context, changed disclosure, or expired draft is rejected rather than silently sent.
98
102
 
99
- The daemon reads a bounded private `plugins/extensions.json` manifest and preflights its complete inventory before importing listed TypeScript/JavaScript entry modules. Each default export must match the manifest ID/version and known hook names. Source digests appear in the loaded extension metadata. No directory scanning, package installation or hot reload occurs; changes require a full daemon process restart. The routed agent emits `memory.updated` only after a successful conditional write, with its path and committed revision. A notification failure does not undo that write or replay it.
103
+ `textbutler replies show DRAFT` exposes every ordered disclosed action, exact recipient, attachment hash and review digest. `textbutler replies send DRAFT DIGEST` sends only that exact reviewed draft, and `textbutler replies send CONTACT TEXT...` sends literal owner text through the identical grant, plan, journal, disclosure and reconciliation discipline as an automatic reply. `textbutler replies discard DRAFT` drops a suggestion. The menu bar exposes scan, per-conversation suggestion, labeled draft previews and discard. Complete review and digest-bound sending happen in the terminal or CLI; a truncated preview cannot authorize a send.
100
104
 
101
- ## Agentrouter
105
+ An owner send reuses the contact's live standing grant when it covers the needed action kinds with remaining quota. Otherwise the daemon issues a tightly scoped grant: only the specific action kinds, ten-minute expiry, quota equal to the action count. The scoped grant is journaled with intent and pending state, published to the conversation, and revoked after the send when the contact is disabled. One serialized work registration covers grant issuance and dispatch together so delegated renewal and disable-revocation cannot race an in-flight send. The agent never sees this surface; it has no send authority in either direction.
102
106
 
103
- Agentrouter begins as an MIT-licensed source package independent of Textbutler's product model. Its account lease coordinates the selected provider account without embedding credentials in a contact workspace. Credential resolvers remain trusted host services. A lease cannot be stolen merely because its time elapsed while a process might still be alive.
107
+ `replies.send` returns `submitted`, `failed`, `partial`, `cancelled`, or `indeterminate`. An indeterminate owner send blocks the next reply for that contact — automatic or owner-initiated — until the journaled intent is reconciled, exactly like an automatic send.
104
108
 
105
- Provider adapters declare observed, exact-version qualification. A launch plan is not proof of a sandbox. Codex's shell-disable setting and Claude's exact tool list are useful inputs, but an adapter is not admitted until attempted shell/process calls, host file reads, inherited MCP/plugin configuration, auth-file access, alternate agents, and additional workspaces are demonstrably blocked. No bypass-permissions mode is acceptable.
109
+ ## Hooks and plugins
106
110
 
107
- The source implements a pinned Claude Agent SDK adapter with explicit API-key account binding, a private verified executable snapshot, isolated runtime directories, no built-in tools or inherited settings, broker-only MCP, bounded raw output, and joined process-group termination. Its production gate requires independent qualification of the exact executable and SDK identity. Synthetic protocol and native-runtime fixtures do not activate it. The experimental Codex app-server driver verifies each model request's tool inventory through a host relay and keeps contact storage outside native scratch. It has no live account transport or production registration; native confinement and adversarial custody qualification remain incomplete. See the [Agentrouter implementation and evidence](../../packages/agentrouter/README.md).
111
+ The initial lifecycle is `message.received`, `reply.decide`, `reply.compose`, `reply.before-send`, `reply.sent`, `memory.updated`, and `run.failed`. Hooks have a named/versioned owner-installed extension, deterministic registration order, a deadline, and a cancellation signal. Failure before dispatch closes admission. A notification-hook failure after a receipt cannot change that receipt or trigger resend.
108
112
 
109
- A separately selected Claude API adapter executes the bounded tool loop in the trusted host. It exposes only the six broker tools and never starts a model-selected process. Account checks bind the actual packaged runtime and private credential generation; replacing a credential invalidates old discovery and running account work. Fresh model availability and price metadata select a cheap classifier. This explicit API choice does not silently replace Claude Code or Codex and does not use their subscription authentication.
113
+ Executable extensions are application code with the daemon's trust. They are installed outside contact folders; the agent cannot write them or turn message text into imports. Agent self-evolution means revising guidance and memory, not installing executable code. A future untrusted plugin mode needs its own process or language sandbox and explicit capabilities. In-process hooks are never described as a plugin security boundary.
110
114
 
111
- The model receives a fixed contact/workspace identity and a fixed run ID. File operations are brokered and conditional. Public web requests are separately bounded and must not reach loopback, private networks, local sockets, or cloud metadata through DNS or redirects. Message tools stage recipient-free intents for the one conversation. Unknown tool names and unknown input fields fail. Credential/account services are never model tools.
115
+ The daemon reads a bounded private `plugins/extensions.json` manifest and preflights its complete inventory before importing listed TypeScript/JavaScript entry modules. Each default export must match the manifest ID/version and known hook names. Source digests appear in the loaded extension metadata. No directory scanning, package installation or hot reload occurs; changes require a full daemon process restart. The routed agent emits `memory.updated` only after a successful conditional write, with its path and committed revision. A notification failure does not undo that write or replay it.
112
116
 
113
- Oompa's existing runtime provider port and account/process-custody patterns are source references. Its ordinary workspace-write execution profile is not the requested contact-only sandbox. AI Charts is a prospective second consumer. Do not migrate either product to Agentrouter until an adapter has equivalent feature and recovery evidence; avoid changing their active work in this redesign.
117
+ ## xcb application contract
118
+
119
+ Textbutler is an MIT-licensed reference application for
120
+ [xcb](https://github.com/hraness/xcb). AI execution requires a verified Textbutler
121
+ bundle whose build validates independently reviewed composition evidence against
122
+ current source bytes and both contact capability profiles. The build embeds
123
+ this application admission separately from xcb's provider admission. Source
124
+ daemon startup carries no composition admission and cannot enable this route. The native `generate` process provides a
125
+ bounded application request/result interface over stdin and stdout. The owner
126
+ pins the physical xcb executable and selects an explicit private state root,
127
+ subscription account and full model key. xcb and the provider executables are
128
+ separate installations; contact memory cannot edit their configuration.
129
+
130
+ xcb generates with zero provider tools and no inherited coding session. It owns
131
+ provider credentials, runtime admission, operating-system confinement and
132
+ process cleanup. Textbutler sends bounded contact context, validates the
133
+ structured response and accepts output only with a settled execution receipt.
134
+ A hash match or a root process exit alone cannot establish this receipt.
135
+ Uncertain cleanup preserves custody and blocks another invocation.
136
+
137
+ For replies, the model can propose a Textbutler operation. The application
138
+ validates the exact operation and closed input, calls its contact-bound broker,
139
+ and includes a bounded result in the next inference step. Classification
140
+ advertises no operations. The native provider never receives a filesystem or
141
+ messaging tool. The [subscription guide](native-subscription.md) documents setup,
142
+ limits and recovery. Credentials remain in xcb; grant and send authority remain
143
+ in Textbutler and Ghostget.
144
+
145
+ The retained AgentMixer compatibility library supplies shared application types
146
+ and broker helpers. Its historical package identity remains pinned for
147
+ reproducible builds; applications need not import private xcb internals or share
148
+ a source checkout. The native process contract is the subscription boundary.
149
+
150
+ A separately selected Claude API adapter executes a bounded tool loop in the
151
+ trusted host. That route still requires independent packaged-runtime admission,
152
+ an explicit API credential and current model/price evidence. Neither source
153
+ startup nor local distribution integrity provides this admission, and an xcb
154
+ subscription failure cannot switch to API billing.
155
+
156
+ The model receives a fixed contact/workspace identity and run ID. File
157
+ operations are brokered and conditional. Public web requests are bounded and
158
+ cannot reach private networks, local sockets or cloud metadata through DNS or
159
+ redirects. Messaging operations stage recipient-bound intents. Credential and
160
+ account controls are never model tools.
114
161
 
115
162
  ## Ghostget contract and rich features
116
163
 
@@ -134,14 +181,14 @@ Ghostget currently imports published Message Like Me bundle contracts. Keep that
134
181
 
135
182
  ## macOS menu companion
136
183
 
137
- The supported local surface is an unbundled status-item companion launched by the CLI. It exposes daemon state, contact and account readiness, capabilities, recent activity, pause/resume and status refresh. The website action opens the informational textbutler.app page. Contact enrollment, account setup and other settings remain daemon protocol capabilities without a current menu or web interface. The companion is not required to run the daemon.
184
+ The status-item companion can be launched by the CLI or the native app. It exposes daemon state, contact and account readiness, capabilities, recent activity, pause/resume, conversation selection and the reply inbox. The terminal supports complete draft review and setup. The website action opens the informational textbutler.app page. The companion is not required to run the daemon.
138
185
 
139
186
  One narrow native command accepts the versioned control request. It connects to the private user socket, bounds requests/responses, applies timeouts, and verifies same-user ownership. The companion has no generic shell, filesystem, opener, or network plugin and does not inherit access to arbitrary Ghostget operations.
140
187
 
141
188
  ## Admission still required
142
189
 
143
- 1. Use the verified Ghostget 0.18.2 package, which includes the reviewed automation source and native helpers. Real account synchronization, recipient identity, rich actions and revocation still require a bounded owner-authorized live test; artifact admission and synthetic fixtures do not prove delivery.
144
- 2. Independently qualify the native Claude SDK and Codex adapters for the requested no-shell, contact-only profile before enabling those choices. The separate Claude API path requires explicit account setup and packaged-runtime admission.
190
+ 1. This development version pins Ghostget 0.18.21 for native TextButler iMessage setup; matching artifact admission and live conversation checks remain pending. Preserve its native helper resource bundle, protected-folder startup fix, partial discovery that excludes chats without usable participant metadata, and bounded discovery diagnostics without message bodies. Real account synchronization, recipient identity, rich actions and revocation still require a bounded owner-authorized live test; artifact admission and synthetic fixtures do not prove delivery.
191
+ 2. Use a verified Textbutler bundle with reviewed composition admission and connect an admitted native xcb build through its zero-tool generation contract. Verify the exact provider/account, both classifier and reply behavior, cancellation and uncertain-custody recovery before enabling automatic replies. The separate Claude API path retains explicit account setup and packaged-runtime admission.
145
192
  3. Publish the CLI package with its pinned desktop-foundation SDK dependency. Verify the package bytes, the verified pinned runner download, singleton behavior, and the shared autostart install/uninstall lifecycle. A source checkout or missing companion must never trigger a build at launch.
146
193
  4. Keep historical repository and published package identities as compatibility and provenance anchors. The Textbutler site is assigned to `textbutler.app`; later identity migrations must preserve immutable artifacts and existing release protections.
147
194