granttap-mcp 0.8.17 → 0.8.19
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.
- package/README.md +50 -14
- package/apps/bridge/src/approvals/api.ts +306 -0
- package/apps/bridge/src/{approval.ts → approvals/index.ts} +74 -68
- package/apps/bridge/src/approvals/records.ts +378 -0
- package/apps/bridge/src/approvals/state.ts +2 -0
- package/apps/bridge/src/approvals/types.ts +76 -0
- package/apps/bridge/src/bin/{connect.ts → cli/connect.ts} +3 -3
- package/apps/bridge/src/bin/{mesh-connect.ts → cli/mesh-connect.ts} +14 -4
- package/apps/bridge/src/bin/{pair-uri.ts → cli/pair-uri.ts} +2 -2
- package/apps/bridge/src/bin/{reset.ts → cli/reset.ts} +1 -1
- package/apps/bridge/src/bin/{setup.ts → cli/setup.ts} +10 -10
- package/apps/bridge/src/bin/{claude-hook.ts → hooks/claude-hook.ts} +11 -8
- package/apps/bridge/src/bin/{claude-prompt-hook.ts → hooks/claude-prompt-hook.ts} +1 -1
- package/apps/bridge/src/bin/{codex-hook.ts → hooks/codex-hook.ts} +7 -7
- package/apps/bridge/src/bin/{codex-policy-hook.ts → hooks/codex-policy-hook.ts} +9 -6
- package/apps/bridge/src/bin/{cursor-after-shell.ts → hooks/cursor-after-shell.ts} +4 -4
- package/apps/bridge/src/bin/{cursor-hook.ts → hooks/cursor-hook.ts} +8 -29
- package/apps/bridge/src/bin/hooks/shared/cursor-output.ts +21 -0
- package/apps/bridge/src/bin/{cursor-mcp-hook.ts → runtime/cursor-mcp-hook.ts} +13 -34
- package/apps/bridge/src/bin/{monitor.ts → runtime/monitor.ts} +10 -5
- package/apps/bridge/src/bin/runtime/restrictions-check.ts +8 -0
- package/apps/bridge/src/capabilities/descriptors.ts +43 -3
- package/apps/bridge/src/capabilities/index.ts +42 -0
- package/apps/bridge/src/capabilities/metadata.ts +4 -10
- package/apps/bridge/src/capabilities/skill-bundle.ts +40 -0
- package/apps/bridge/src/capabilities/skills.ts +157 -15
- package/apps/bridge/src/config/{capability-policy.ts → access/capability-policy.ts} +1 -1
- package/apps/bridge/src/config/{pairing.ts → access/pairing.ts} +7 -8
- package/apps/bridge/src/config/access/write-private.ts +17 -0
- package/apps/bridge/src/config/commands.ts +164 -0
- package/apps/bridge/src/{config.ts → config/index.ts} +5 -5
- package/apps/bridge/src/config/{runtime.ts → runtime/index.ts} +68 -10
- package/apps/bridge/src/config/runtime/node-bin.ts +40 -0
- package/apps/bridge/src/cortex/config.ts +36 -0
- package/apps/bridge/src/cortex/integration.ts +202 -0
- package/apps/bridge/src/{attachment-store.ts → delivery/attachment-store.ts} +2 -2
- package/apps/bridge/src/{delivery.ts → delivery/index.ts} +1 -1
- package/apps/bridge/src/engine/README.md +16 -1
- package/apps/bridge/src/engine/invocation/decision-parse.ts +35 -0
- package/apps/bridge/src/engine/invocation/ingest.ts +179 -0
- package/apps/bridge/src/engine/invocation/log-reader.ts +72 -0
- package/apps/bridge/src/engine/invocation/parse.ts +120 -0
- package/apps/bridge/src/engine/invocation/query.ts +53 -0
- package/apps/bridge/src/engine/invocation/scope.ts +39 -0
- package/apps/bridge/src/engine/{engine-declaration.ts → protocol/engine-declaration.ts} +2 -2
- package/apps/bridge/src/engine/protocol/engine-invocation-protocol.ts +117 -0
- package/apps/bridge/src/engine/protocol/engine-memory-protocol.ts +82 -0
- package/apps/bridge/src/engine/protocol/engine-protocol.ts +40 -0
- package/apps/bridge/src/engine/protocol/transport/framing.ts +61 -0
- package/apps/bridge/src/engine/protocol/transport/protocol-base.ts +23 -0
- package/apps/bridge/src/engine/protocol/transport/protocol-types.ts +125 -0
- package/apps/bridge/src/engine/protocol/transport/result-parser.ts +250 -0
- package/apps/bridge/src/engine/{engine-client.ts → runtime/engine-client.ts} +8 -1
- package/apps/bridge/src/engine/runtime/engine-memory.ts +116 -0
- package/apps/bridge/src/engine/runtime/engine-projects.ts +276 -0
- package/apps/bridge/src/engine/{engine-supervisor.ts → runtime/engine-supervisor.ts} +4 -3
- package/apps/bridge/src/engine/runtime/repository-graph-jobs.ts +55 -0
- package/apps/bridge/src/{adapters.ts → host/adapters.ts} +2 -2
- package/apps/bridge/src/host/instance-epoch.ts +69 -0
- package/apps/bridge/src/{session-keys.ts → host/session-keys.ts} +20 -16
- package/apps/bridge/src/install/hooks.ts +328 -0
- package/apps/bridge/src/install/index.ts +26 -0
- package/apps/bridge/src/install/inspect.ts +340 -0
- package/apps/bridge/src/{launchd-safety.ts → install/launchd-safety.ts} +43 -0
- package/apps/bridge/src/install/monitor-helper.ts +320 -0
- package/apps/bridge/src/{windows-service.ts → install/windows-service.ts} +9 -3
- package/apps/bridge/src/machine-load/{agent-load-history.ts → host/agent-load-history.ts} +1 -1
- package/apps/bridge/src/machine-load/{loop.ts → host/loop.ts} +1 -1
- package/apps/bridge/src/machine-load/{process-sampler.ts → host/process-sampler.ts} +1 -1
- package/apps/bridge/src/machine-load/index.ts +4 -4
- package/apps/bridge/src/machine-load/{mcp-load-cache.ts → mcp/cache.ts} +2 -2
- package/apps/bridge/src/machine-load/{mcp-process-sampler.ts → mcp/process-sampler.ts} +1 -1
- package/apps/bridge/src/machine-load/{mcp-load-refresh.ts → mcp/refresh.ts} +6 -6
- package/apps/bridge/src/mesh/README.md +7 -0
- package/apps/bridge/src/mesh/{capsule.ts → admin/capsule.ts} +3 -3
- package/apps/bridge/src/mesh/{checkpoint.ts → admin/checkpoint.ts} +1 -1
- package/apps/bridge/src/mesh/{convergence.ts → admin/convergence.ts} +1 -1
- package/apps/bridge/src/mesh/{admin.ts → admin/index.ts} +2 -2
- package/apps/bridge/src/mesh/{journal.ts → admin/journal.ts} +36 -12
- package/apps/bridge/src/mesh/{binding-state.ts → catalog/binding-state.ts} +2 -2
- package/apps/bridge/src/mesh/{capability.ts → catalog/capability.ts} +4 -4
- package/apps/bridge/src/mesh/{catalog.ts → catalog/index.ts} +100 -93
- package/apps/bridge/src/mesh/catalog/manifest.ts +20 -0
- package/apps/bridge/src/mesh/catalog/models.ts +47 -0
- package/apps/bridge/src/mesh/catalog/project/capabilities.ts +56 -0
- package/apps/bridge/src/mesh/catalog/project/configured-mcp.ts +80 -0
- package/apps/bridge/src/mesh/catalog/project/observations.ts +92 -0
- package/apps/bridge/src/mesh/catalog/project/requests.ts +72 -0
- package/apps/bridge/src/mesh/context/delta.ts +19 -0
- package/apps/bridge/src/mesh/context/env.ts +149 -0
- package/apps/bridge/src/mesh/context/packet.ts +54 -0
- package/apps/bridge/src/mesh/context/project.ts +155 -0
- package/apps/bridge/src/mesh/{prompt-context.ts → context/prompt.ts} +8 -8
- package/apps/bridge/src/mesh/delivery/affected-recipients.ts +48 -0
- package/apps/bridge/src/mesh/delivery/broadcast-ledger.ts +47 -0
- package/apps/bridge/src/mesh/delivery/index.ts +49 -0
- package/apps/bridge/src/mesh/{run-digest.ts → delivery/run-digest.ts} +19 -18
- package/apps/bridge/src/mesh/handoff/delegation-loop.ts +46 -0
- package/apps/bridge/src/mesh/{handoff.ts → handoff/index.ts} +1 -1
- package/apps/bridge/src/mesh/handoff/invite-source.ts +25 -0
- package/apps/bridge/src/mesh/{other-side.ts → handoff/other-side.ts} +72 -19
- package/apps/bridge/src/mesh/{call-scope.ts → identity/call-scope.ts} +1 -1
- package/apps/bridge/src/mesh/{computer-identity.ts → identity/computer.ts} +1 -1
- package/apps/bridge/src/mesh/identity/device-alias.ts +37 -0
- package/apps/bridge/src/mesh/{endpoint.ts → identity/endpoint.ts} +14 -7
- package/apps/bridge/src/mesh/{local.ts → local-remote/local.ts} +2 -2
- package/apps/bridge/src/mesh/{remote.ts → local-remote/remote.ts} +1 -1
- package/apps/bridge/src/mesh/{observed-claims.ts → observed/claims.ts} +12 -7
- package/apps/bridge/src/mesh/{integration-map.ts → observed/integration-map.ts} +4 -10
- package/apps/bridge/src/mesh/restrictions/check.ts +73 -0
- package/apps/bridge/src/mesh/restrictions/cli.ts +40 -0
- package/apps/bridge/src/mesh/restrictions/evaluate.ts +100 -0
- package/apps/bridge/src/mesh/restrictions/index.ts +19 -0
- package/apps/bridge/src/mesh/restrictions/store.ts +98 -0
- package/apps/bridge/src/mesh/restrictions/write-gate.ts +63 -0
- package/apps/bridge/src/mesh/runtime/capabilities/capability-inventory.ts +9 -0
- package/apps/bridge/src/mesh/runtime/capabilities/execution-capabilities.ts +34 -0
- package/apps/bridge/src/mesh/runtime/capabilities/index.ts +2 -0
- package/apps/bridge/src/mesh/runtime/capabilities/session-discovery.ts +12 -0
- package/apps/bridge/src/mesh/{runtime-dependencies.ts → runtime/dependencies.ts} +6 -4
- package/apps/bridge/src/mesh/runtime/execution-policy.ts +127 -0
- package/apps/bridge/src/mesh/{execution-sweep.ts → runtime/execution-sweep.ts} +1 -1
- package/apps/bridge/src/mesh/runtime/graph/binding-sync.ts +31 -0
- package/apps/bridge/src/mesh/runtime/handoff.ts +204 -0
- package/apps/bridge/src/mesh/runtime/index.ts +334 -0
- package/apps/bridge/src/mesh/{worktree.ts → runtime/worktree.ts} +9 -8
- package/apps/bridge/src/mesh/{map.ts → snapshot/map.ts} +57 -24
- package/apps/bridge/src/mesh/snapshot/merge.ts +51 -0
- package/apps/bridge/src/mesh/{scoped-view.ts → snapshot/scoped-view.ts} +36 -11
- package/apps/bridge/src/mesh/snapshot/window.ts +87 -0
- package/apps/bridge/src/mesh/store/base.ts +375 -0
- package/apps/bridge/src/mesh/store/index.ts +389 -0
- package/apps/bridge/src/mesh/store/rejoin.ts +170 -0
- package/apps/bridge/src/mesh/store/state.ts +165 -0
- package/apps/bridge/src/mesh/{store-support.ts → store/support.ts} +25 -5
- package/apps/bridge/src/mesh/{store-sync.ts → store/sync.ts} +8 -6
- package/apps/bridge/src/mesh/{attention.ts → tasks/attention.ts} +1 -1
- package/apps/bridge/src/mesh/tasks/create-task.ts +84 -0
- package/apps/bridge/src/mesh/tasks/queue.ts +54 -0
- package/apps/bridge/src/mesh/{readiness.ts → tasks/readiness.ts} +1 -1
- package/apps/bridge/src/mesh/{task-state.ts → tasks/state.ts} +2 -2
- package/apps/bridge/src/monitor/graph-refresh.ts +17 -0
- package/apps/bridge/src/monitor/handlers/catalog.ts +63 -0
- package/apps/bridge/src/monitor/handlers/inbound.ts +232 -0
- package/apps/bridge/src/monitor/handlers/receipts.ts +77 -0
- package/apps/bridge/src/monitor/handlers/session-commands.ts +165 -0
- package/apps/bridge/src/monitor/handlers/task-create.ts +109 -0
- package/apps/bridge/src/monitor/handlers/user-message.ts +156 -0
- package/apps/bridge/src/monitor/history/pages.ts +60 -0
- package/apps/bridge/src/monitor/index.ts +21 -0
- package/apps/bridge/src/{monitor-heartbeat.ts → monitor/support/heartbeat.ts} +19 -1
- package/apps/bridge/src/{monitor-leadership.ts → monitor/support/leadership.ts} +59 -47
- package/apps/bridge/src/{monitor-publish-loop.ts → monitor/support/publish-loop.ts} +3 -1
- package/apps/bridge/src/monitor/support/runtime.ts +305 -0
- package/apps/bridge/src/{monitor-session-activity.ts → monitor/support/session-activity.ts} +2 -2
- package/apps/bridge/src/pairing/controllers.ts +62 -0
- package/apps/bridge/src/pairing/index.ts +264 -0
- package/apps/bridge/src/pairing/presence.ts +57 -0
- package/apps/bridge/src/{pair-uri-file.ts → pairing/uri-file.ts} +1 -1
- package/apps/bridge/src/policy/capability-fingerprint.ts +28 -5
- package/apps/bridge/src/policy/codex-project-ask.ts +1 -1
- package/apps/bridge/src/policy/decision-log.ts +24 -2
- package/apps/bridge/src/policy/effective-action.ts +69 -17
- package/apps/bridge/src/policy/governed-projects.ts +1 -1
- package/apps/bridge/src/{policy.ts → policy/index.ts} +13 -1
- package/apps/bridge/src/project-policy/apply.ts +236 -0
- package/apps/bridge/src/project-policy/mapping.ts +2 -2
- package/apps/bridge/src/project-policy/runtime.ts +17 -227
- package/apps/bridge/src/project-policy/types.ts +46 -0
- package/apps/bridge/src/{claude-bin.ts → providers/claude-bin.ts} +1 -1
- package/apps/bridge/src/providers/codex-bin.ts +33 -0
- package/apps/bridge/src/{codex-control.ts → providers/codex-control.ts} +2 -3
- package/apps/bridge/src/{cursor-mcp-policy.ts → providers/cursor-mcp-policy.ts} +2 -15
- package/apps/bridge/src/{reply.ts → reply/index.ts} +48 -25
- package/apps/bridge/src/reply/{attachments.ts → payload/attachments.ts} +2 -2
- package/apps/bridge/src/reply/{routing.ts → payload/routing.ts} +1 -1
- package/apps/bridge/src/reply/{process.ts → process/index.ts} +30 -8
- package/apps/bridge/src/reply/{provider-headless.ts → process/provider-headless.ts} +23 -10
- package/apps/bridge/src/sessions/cursor/activity.ts +22 -22
- package/apps/bridge/src/sessions/cursor/catalog.ts +111 -28
- package/apps/bridge/src/sessions/cursor/index.ts +12 -0
- package/apps/bridge/src/sessions/cursor/scan.ts +119 -31
- package/apps/bridge/src/sessions/cursor/transcripts.ts +21 -20
- package/apps/bridge/src/{sessions.ts → sessions/index.ts} +68 -37
- package/apps/bridge/src/sessions/scan/claude/activity.ts +276 -0
- package/apps/bridge/src/sessions/scan/claude/index.ts +3 -0
- package/apps/bridge/src/sessions/scan/claude/parse.ts +165 -0
- package/apps/bridge/src/sessions/scan/claude/scan.ts +36 -0
- package/apps/bridge/src/sessions/scan/claude/shared.ts +190 -0
- package/apps/bridge/src/sessions/scan/codex/activity.ts +347 -0
- package/apps/bridge/src/sessions/scan/codex/assemble.ts +153 -0
- package/apps/bridge/src/sessions/scan/codex/index.ts +3 -0
- package/apps/bridge/src/sessions/scan/codex/parse.ts +174 -0
- package/apps/bridge/src/sessions/scan/codex/scan.ts +33 -0
- package/apps/bridge/src/sessions/scan/codex/shared.ts +201 -0
- package/apps/bridge/src/sessions/{grok.ts → scan/grok.ts} +13 -13
- package/apps/bridge/src/sessions/{activity-helpers.ts → support/activity-helpers.ts} +44 -17
- package/apps/bridge/src/sessions/{capability-totals.ts → support/capability-totals.ts} +0 -0
- package/apps/bridge/src/sessions/{child-threads.ts → support/child-threads.ts} +7 -5
- package/apps/bridge/src/sessions/{common.ts → support/common.ts} +9 -7
- package/apps/bridge/src/sessions/{telemetry.ts → telemetry/index.ts} +9 -9
- package/apps/bridge/src/tools/updater.ts +9 -5
- package/apps/mcp/src/bin/authorize.ts +5 -6
- package/apps/mcp/src/bin/serve.ts +4 -5
- package/apps/mcp/src/bin/status.ts +2 -2
- package/apps/mcp/src/connection-center/README.md +3 -2
- package/apps/mcp/src/connection-center/enrollment.ts +44 -0
- package/apps/mcp/src/connection-center/state.ts +32 -9
- package/apps/mcp/src/connection-center/view.js +42 -10
- package/apps/mcp/src/connection-center/widget.html +5 -4
- package/apps/mcp/src/cursor-config.ts +35 -3
- package/apps/mcp/src/{create-server.ts → http/create-server.ts} +7 -7
- package/apps/mcp/src/http/http-server.ts +106 -0
- package/apps/mcp/src/http/mcp-handler.ts +62 -0
- package/apps/mcp/src/http/oauth-routes.ts +109 -0
- package/apps/mcp/src/http-service/common.ts +4 -0
- package/apps/mcp/src/{http-service.ts → http-service/index.ts} +4 -4
- package/apps/mcp/src/http-service/installer.ts +4 -4
- package/apps/mcp/src/http-service/snapshot.ts +2 -2
- package/apps/mcp/src/mcp-tools/README.md +2 -1
- package/apps/mcp/src/mcp-tools/{connect.ts → connect/index.ts} +39 -29
- package/apps/mcp/src/mcp-tools/{relay.ts → connect/relay.ts} +46 -13
- package/apps/mcp/src/mcp-tools/{connection-widget.ts → connect/widget.ts} +3 -3
- package/apps/mcp/src/mcp-tools/interaction/answers.ts +39 -0
- package/apps/mcp/src/mcp-tools/interaction/index.ts +27 -0
- package/apps/mcp/src/mcp-tools/interaction/notify.ts +130 -0
- package/apps/mcp/src/mcp-tools/interaction/publish.ts +154 -0
- package/apps/mcp/src/mcp-tools/interaction/questions.ts +75 -0
- package/apps/mcp/src/mcp-tools/{mesh-actions.ts → mesh/actions.ts} +38 -7
- package/apps/mcp/src/mcp-tools/mesh/current.ts +100 -0
- package/apps/mcp/src/mcp-tools/{mesh-relay.ts → mesh/relay.ts} +4 -4
- package/apps/mcp/src/mcp-tools/mesh/resource.ts +68 -0
- package/apps/mcp/src/mcp-tools/mesh/session-env.ts +13 -0
- package/apps/mcp/src/mcp-tools/mesh/skills/resource.ts +52 -0
- package/apps/mcp/src/mcp-tools/{mesh-tools.ts → mesh/tools.ts} +1 -1
- package/apps/mcp/src/{mesh-server.ts → mesh/mesh-server.ts} +1 -1
- package/apps/mcp/src/oauth/README.md +1 -1
- package/apps/mcp/src/oauth/consent/after-consent.ts +31 -0
- package/apps/mcp/src/oauth/{pairing-view.ts → consent/pairing-view.ts} +74 -19
- package/apps/mcp/src/oauth/consent/pending.ts +64 -0
- package/apps/mcp/src/oauth/consent/website-session.ts +180 -0
- package/apps/mcp/src/oauth/session/connect-snapshot.ts +91 -0
- package/apps/mcp/src/oauth/{loopback-origin.ts → session/loopback-origin.ts} +7 -1
- package/apps/mcp/src/oauth/session/mailbox-claim.ts +84 -0
- package/apps/mcp/src/oauth/{store.ts → session/store.ts} +4 -21
- package/apps/mcp/src/oauth-provider.ts +82 -21
- package/apps/mcp/src/server.ts +5 -3
- package/apps/mcp/src/{package-version.ts → status/package-version.ts} +1 -1
- package/apps/mcp/src/status/pairing-status.ts +71 -0
- package/apps/mcp/src/{provider-status.ts → status/provider-status.ts} +6 -9
- package/bin/granttap-mcp.mjs +26 -12
- package/cursor-plugin/.cursor-plugin/plugin.json +3 -3
- package/cursor-plugin/README.md +40 -37
- package/cursor-plugin/commands/connect.md +8 -14
- package/cursor-plugin/mcp.json +5 -2
- package/cursor-plugin/skills/connect/SKILL.md +19 -75
- package/cursor-plugin/stdio-bootstrap.cjs +60 -0
- package/docs/cursor-authorize.md +14 -8
- package/docs/images/iphone-task-route.png +0 -0
- package/package.json +5 -4
- package/packages/core/crypto.ts +35 -0
- package/packages/core/relay-client-types.ts +4 -0
- package/packages/core/relay-client.ts +34 -7
- package/packages/core/replay-store.ts +34 -0
- package/packages/protocol/messages/capabilities.ts +1 -0
- package/packages/protocol/messages/{interaction.ts → interaction/index.ts} +39 -3
- package/packages/protocol/messages/{mesh.ts → mesh/events.ts} +2 -159
- package/packages/protocol/messages/mesh/index.ts +5 -0
- package/packages/protocol/messages/mesh/invocations.ts +81 -0
- package/packages/protocol/messages/mesh/knowledge/index.ts +19 -0
- package/packages/protocol/messages/mesh/snapshot.ts +317 -0
- package/packages/protocol/messages/mesh/tasks.ts +117 -0
- package/packages/protocol/messages/pairing-join.ts +36 -0
- package/packages/protocol/messages/project-policy.ts +112 -1
- package/packages/protocol/messages/{sessions.ts → sessions/index.ts} +47 -3
- package/packages/protocol/schema.ts +17 -0
- package/apps/bridge/src/approval-state.ts +0 -717
- package/apps/bridge/src/capabilities.ts +0 -27
- package/apps/bridge/src/engine/engine-projects.ts +0 -68
- package/apps/bridge/src/engine/engine-protocol.ts +0 -265
- package/apps/bridge/src/install.ts +0 -817
- package/apps/bridge/src/mesh/runtime-handoff.ts +0 -184
- package/apps/bridge/src/mesh/runtime.ts +0 -201
- package/apps/bridge/src/mesh/snapshot-merge.ts +0 -32
- package/apps/bridge/src/mesh/store-state.ts +0 -292
- package/apps/bridge/src/mesh/store.ts +0 -666
- package/apps/bridge/src/monitor.ts +0 -786
- package/apps/bridge/src/pairing.ts +0 -136
- package/apps/bridge/src/sessions/claude.ts +0 -613
- package/apps/bridge/src/sessions/codex.ts +0 -776
- package/apps/bridge/src/sessions/cursor.ts +0 -9
- package/apps/mcp/src/http-server.ts +0 -249
- package/apps/mcp/src/mcp-tools/interaction.ts +0 -407
- package/apps/mcp/src/mcp-tools/mesh-resource.ts +0 -134
- package/apps/mcp/src/pairing-status.ts +0 -25
- /package/apps/bridge/src/config/{paths.ts → runtime/paths.ts} +0 -0
- /package/apps/bridge/src/engine/{engine-policy-protocol.ts → protocol/engine-policy-protocol.ts} +0 -0
- /package/apps/bridge/src/engine/{engine-policy-types.ts → protocol/engine-policy-types.ts} +0 -0
- /package/apps/bridge/src/engine/{engine-health.ts → runtime/engine-health.ts} +0 -0
- /package/apps/bridge/src/{self-protection.ts → host/self-protection.ts} +0 -0
- /package/apps/bridge/src/machine-load/{disk-usage.ts → host/disk-usage.ts} +0 -0
- /package/apps/bridge/src/machine-load/{scan-cost.ts → mcp/scan-cost.ts} +0 -0
- /package/apps/bridge/src/mesh/{identity.ts → identity/index.ts} +0 -0
- /package/apps/bridge/src/mesh/{observed-writes.ts → observed/writes.ts} +0 -0
- /package/apps/bridge/src/{monitor-single-flight.ts → monitor/support/single-flight.ts} +0 -0
- /package/apps/bridge/src/reply/{types.ts → payload/types.ts} +0 -0
- /package/apps/bridge/src/reply/{cursor-agent-bin.ts → process/cursor-agent-bin.ts} +0 -0
- /package/apps/bridge/src/sessions/{codex-head.ts → support/codex-head.ts} +0 -0
- /package/apps/bridge/src/sessions/{edit-stats.ts → support/edit-stats.ts} +0 -0
- /package/apps/mcp/src/{mesh-stdio.ts → mesh/mesh-stdio.ts} +0 -0
- /package/packages/protocol/messages/{mesh-endpoint.ts → mesh/endpoint.ts} +0 -0
package/README.md
CHANGED
|
@@ -54,15 +54,38 @@ and completion events through the existing `notify` tool, and read state from a
|
|
|
54
54
|
Mesh resource. Full transcripts and hidden reasoning are never mesh payloads.
|
|
55
55
|
Claims have TTLs; a colliding claim is rejected before it is recorded so agents
|
|
56
56
|
can choose different work or contact the owner before escalating to Needs You.
|
|
57
|
+
Observed edit-tool requests are intent claims, not confirmed writes. Resource
|
|
58
|
+
identity includes the repository; equal relative paths from separate repositories
|
|
59
|
+
do not conflict, and another checkout is a coordination warning. The path
|
|
60
|
+
matcher respects segment boundaries, so `src/auth.ts` and `src/auth.ts.bak`
|
|
61
|
+
are distinct. A live repository reading refreshes HEAD after commit or checkout.
|
|
62
|
+
|
|
63
|
+
When a separately distributed, verified GrantTap Engine is enabled on the
|
|
64
|
+
computer, the bridge sends content-free Invocation events before its small Usage
|
|
65
|
+
buffer is trimmed. Claude Code, Codex, and Cursor transcript scanners record
|
|
66
|
+
native call identity, request, reported outcome, resource path, and explicit
|
|
67
|
+
source gaps. The Engine owns the durable journal and bounded cursor history;
|
|
68
|
+
the phone requests the latest or older page under the Project E2EE key. A
|
|
69
|
+
reported successful tool call alone does not prove a filesystem change, and
|
|
70
|
+
unavailable Engine history is reported as unavailable.
|
|
71
|
+
The bridge does not yet produce verified filesystem-change events or attach
|
|
72
|
+
revision-bound impact/consumer links to individual Invocations. It leaves that
|
|
73
|
+
evidence unknown instead of treating an edit request or reported success as a
|
|
74
|
+
confirmed content change.
|
|
75
|
+
Claude Code and Codex denials with native call IDs are recorded as separate,
|
|
76
|
+
metadata-only hook events with the applied rule and policy revision; an
|
|
77
|
+
unidentified Cursor decision is not assigned to an invented call. Script and
|
|
78
|
+
`SKILL.md` fingerprints include a content hash when the local file is readable,
|
|
79
|
+
separate from their path identity. Missing content evidence remains unknown.
|
|
57
80
|
|
|
58
81
|
<p align="center">
|
|
59
82
|
<img src="docs/images/iphone-projects-shared.png" width="200" alt="Projects: one owned by this phone, one shared by another">
|
|
60
83
|
<img src="docs/images/iphone-project-mesh.png" width="200" alt="A Project: Governance, members and computers, Mesh status, repositories, Tasks">
|
|
61
|
-
<img src="docs/images/iphone-task-route.png" width="200" alt="A Task:
|
|
84
|
+
<img src="docs/images/iphone-task-route.png" width="200" alt="A Task: executions, Runtime history, and resource claims">
|
|
62
85
|
<img src="docs/images/iphone-handoff.png" width="200" alt="Task handoff: destination, push, and readiness checks">
|
|
63
86
|
</p>
|
|
64
87
|
|
|
65
|
-
<p align="center"><em>Projects, one of them shared by another phone · a Project with its Governance, members, computers, repositories, and Tasks · a Task with
|
|
88
|
+
<p align="center"><em>Projects, one of them shared by another phone · a Project with its Governance, members, computers, repositories, and Tasks · a Task with executions, Runtime history, and claims · a handoff with its readiness checks</em></p>
|
|
66
89
|
|
|
67
90
|
For Claude Code, Codex, and Cursor, the provider hook runs inside the agent's
|
|
68
91
|
own session and sees the exact call. GrantTap attributes every `notify` to the
|
|
@@ -162,6 +185,14 @@ and from then on that computer takes part in the mesh like any other: its
|
|
|
162
185
|
chats are the Project's, its claims are seen by every other computer, and a
|
|
163
186
|
Task can be handed to it.
|
|
164
187
|
|
|
188
|
+
A Project can pin new tasks to one confirmed computer. The endpoint id is
|
|
189
|
+
the stored computer identity, not the display name. Models offered on the
|
|
190
|
+
phone come from that host's catalog. If the pinned host is offline, the
|
|
191
|
+
Project either refuses the create or queues it until a deadline — it does
|
|
192
|
+
not silently start the task elsewhere. A command issued for a previous
|
|
193
|
+
instance of the computer (a restore or clone that kept pairing keys) is
|
|
194
|
+
rejected. A pin is not a secure VM.
|
|
195
|
+
|
|
165
196
|
### A claim released by the person
|
|
166
197
|
|
|
167
198
|
A claim outlives an agent that crashed or was closed, and the files it names
|
|
@@ -223,16 +254,21 @@ grok plugin marketplace add sergii-ziborov/granttap-mcp
|
|
|
223
254
|
grok plugin install granttap --trust
|
|
224
255
|
```
|
|
225
256
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
and
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
257
|
+
For Cursor, use the reviewed **GrantTap** Marketplace listing when it is visible
|
|
258
|
+
to your account. Reload Cursor. On this computer, run `granttap setup`. Pair
|
|
259
|
+
and change settings on [granttap.com/connect](https://granttap.com/connect)
|
|
260
|
+
and in the GrantTap app. Authenticate opens that page even when this Mac is
|
|
261
|
+
already paired — Approve the coding app, Reconnect, or Add another. A scan in
|
|
262
|
+
GrantTap authorizes a new device; a saved pairing does not skip the page.
|
|
263
|
+
Do not add GrantTap in Cursor Customize → MCPs. Cloud **Error · fetch
|
|
264
|
+
failed** is leftover HTTP at `http://127.0.0.1:17342/mcp` — a user MCP row
|
|
265
|
+
or an old plugin `mcp.json`. The Cursor plugin is stdio (`node -e`).
|
|
266
|
+
|
|
267
|
+
Open GrantTap from the plugin connection card. Pairing status, a one-time QR
|
|
268
|
+
when needed, and confirmed reconnect stay on that card, on
|
|
269
|
+
`granttap.com/connect`, and in the GrantTap app. Do not ask an agent to print
|
|
270
|
+
a pairing QR in chat. The same tools still return readable MCP content in
|
|
271
|
+
clients that do not render app UI. Detailed plugin instructions are in
|
|
236
272
|
[`plugins/granttap/README.md`](plugins/granttap/README.md).
|
|
237
273
|
|
|
238
274
|
For the full background helper and provider hooks, install the CLI and run
|
|
@@ -244,8 +280,8 @@ granttap setup
|
|
|
244
280
|
```
|
|
245
281
|
|
|
246
282
|
`granttap setup` detects supported local agents, installs or repairs their
|
|
247
|
-
hooks, installs the background helper,
|
|
248
|
-
|
|
283
|
+
hooks, installs the background helper, removes a leftover user GrantTap MCP
|
|
284
|
+
entry when Cursor is present, and starts phone pairing when run in an
|
|
249
285
|
interactive terminal. It ends with one exact next action.
|
|
250
286
|
|
|
251
287
|
Setup also declares the separately distributed GrantTap Engine, which Project
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import type { RelayClient } from "../../../../packages/core/relay-client";
|
|
3
|
+
import {
|
|
4
|
+
approvalAction,
|
|
5
|
+
type ApprovalDecision,
|
|
6
|
+
type ApprovalRequest,
|
|
7
|
+
type ApprovalResolved,
|
|
8
|
+
type ApprovalsStatus,
|
|
9
|
+
} from "../../../../packages/protocol/schema";
|
|
10
|
+
import {
|
|
11
|
+
MAX_PENDING_AGE_MS,
|
|
12
|
+
MAX_RECORDS,
|
|
13
|
+
TERMINAL_RETENTION_MS,
|
|
14
|
+
claimApprovalWinner,
|
|
15
|
+
effectiveFingerprint,
|
|
16
|
+
matchesRegistration,
|
|
17
|
+
normalizedSessionId,
|
|
18
|
+
pruneRecords,
|
|
19
|
+
readRawRecord,
|
|
20
|
+
readRecordPath,
|
|
21
|
+
recordFiles,
|
|
22
|
+
recordOutcome,
|
|
23
|
+
recordWinner,
|
|
24
|
+
registrationHandle,
|
|
25
|
+
requestDigest,
|
|
26
|
+
requestFingerprint,
|
|
27
|
+
terminalApproval,
|
|
28
|
+
writeRecord,
|
|
29
|
+
type ApprovalAcceptance,
|
|
30
|
+
type ApprovalOutcome,
|
|
31
|
+
type ApprovalRecord,
|
|
32
|
+
type ApprovalRegistration,
|
|
33
|
+
type ApprovalRegistrationHandle,
|
|
34
|
+
type ApprovalTerminalState,
|
|
35
|
+
type PendingApprovalRegistration,
|
|
36
|
+
} from "./records";
|
|
37
|
+
|
|
38
|
+
export function registerPendingApproval(
|
|
39
|
+
request: ApprovalRequest,
|
|
40
|
+
now = Date.now(),
|
|
41
|
+
): ApprovalRegistration {
|
|
42
|
+
const fingerprint = requestFingerprint(request);
|
|
43
|
+
const existing = readRawRecord(request.requestId);
|
|
44
|
+
const existingWinner = existing ? recordWinner(existing) : null;
|
|
45
|
+
const effectiveAt = existingWinner?.acceptedAt ?? existing?.updatedAt;
|
|
46
|
+
if (existing && effectiveAt != null && now - effectiveAt <= TERMINAL_RETENTION_MS) {
|
|
47
|
+
if (effectiveFingerprint(existing) !== fingerprint) {
|
|
48
|
+
return {
|
|
49
|
+
matched: false,
|
|
50
|
+
newlyRegistered: false,
|
|
51
|
+
reason: "conflicting_request",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
matched: true,
|
|
56
|
+
newlyRegistered: false,
|
|
57
|
+
handle: registrationHandle(existing),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const record: ApprovalRecord = {
|
|
61
|
+
request,
|
|
62
|
+
state: "pending",
|
|
63
|
+
updatedAt: now,
|
|
64
|
+
generation: randomUUID(),
|
|
65
|
+
fingerprint,
|
|
66
|
+
};
|
|
67
|
+
writeRecord(record);
|
|
68
|
+
return {
|
|
69
|
+
matched: true,
|
|
70
|
+
newlyRegistered: true,
|
|
71
|
+
handle: registrationHandle(record),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function acceptApprovalDecision(
|
|
76
|
+
decision: ApprovalDecision,
|
|
77
|
+
now = Date.now(),
|
|
78
|
+
expectedRegistration?: ApprovalRegistrationHandle,
|
|
79
|
+
): ApprovalAcceptance {
|
|
80
|
+
const record = readRawRecord(decision.requestId);
|
|
81
|
+
if (!record || !matchesRegistration(record, expectedRegistration)) {
|
|
82
|
+
return { matched: false, newlyResolved: false };
|
|
83
|
+
}
|
|
84
|
+
const requestSessionId = normalizedSessionId(record.request.sessionId);
|
|
85
|
+
const decisionSessionId = normalizedSessionId(decision.sessionId);
|
|
86
|
+
if (requestSessionId !== decisionSessionId) {
|
|
87
|
+
return { matched: false, newlyResolved: false };
|
|
88
|
+
}
|
|
89
|
+
const existingOutcome = recordOutcome(record);
|
|
90
|
+
if (existingOutcome) return acceptance(record, existingOutcome, false);
|
|
91
|
+
if (record.state !== "pending") return { matched: false, newlyResolved: false };
|
|
92
|
+
if (now - record.updatedAt > MAX_PENDING_AGE_MS) {
|
|
93
|
+
return markApprovalTerminal(
|
|
94
|
+
decision.requestId,
|
|
95
|
+
"expired",
|
|
96
|
+
{
|
|
97
|
+
decision: "deny",
|
|
98
|
+
decidedBy: "system",
|
|
99
|
+
note: "Approval request expired before the decision arrived",
|
|
100
|
+
sessionId: requestSessionId,
|
|
101
|
+
},
|
|
102
|
+
now,
|
|
103
|
+
expectedRegistration,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
const storedDecision: ApprovalDecision = {
|
|
107
|
+
...decision,
|
|
108
|
+
sessionId: requestSessionId,
|
|
109
|
+
decidedAt: decision.decidedAt || now,
|
|
110
|
+
};
|
|
111
|
+
const outcome: ApprovalOutcome = { kind: "decision", decision: storedDecision };
|
|
112
|
+
const claim = claimApprovalWinner(record, outcome, now);
|
|
113
|
+
if (!claim.winner) {
|
|
114
|
+
// A corrupt/unreadable winner is fail-closed: do not ACK either contender.
|
|
115
|
+
return { matched: false, newlyResolved: false };
|
|
116
|
+
}
|
|
117
|
+
if (!claim.won) {
|
|
118
|
+
return acceptance(record, claim.winner.outcome, false);
|
|
119
|
+
}
|
|
120
|
+
return acceptance(record, claim.winner.outcome, true);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function acceptance(
|
|
124
|
+
record: ApprovalRecord,
|
|
125
|
+
outcome: ApprovalOutcome,
|
|
126
|
+
newlyResolved: boolean,
|
|
127
|
+
): ApprovalAcceptance {
|
|
128
|
+
return {
|
|
129
|
+
matched: true,
|
|
130
|
+
newlyResolved,
|
|
131
|
+
request: record.request,
|
|
132
|
+
outcome,
|
|
133
|
+
decision: outcome.kind === "decision" ? outcome.decision : undefined,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Read the exact-scope outcome durably accepted by another process. */
|
|
138
|
+
export function resolvedApprovalOutcome(
|
|
139
|
+
requestId: string,
|
|
140
|
+
expectedSessionId: string | null | undefined,
|
|
141
|
+
expectedRegistration?: ApprovalRegistrationHandle,
|
|
142
|
+
): ApprovalOutcome | null {
|
|
143
|
+
const record = readRawRecord(requestId);
|
|
144
|
+
if (
|
|
145
|
+
!record ||
|
|
146
|
+
normalizedSessionId(record.request.sessionId) !== normalizedSessionId(expectedSessionId) ||
|
|
147
|
+
!matchesRegistration(record, expectedRegistration)
|
|
148
|
+
) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
return recordOutcome(record);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Read a decision only when the current request generation and exact scope match. */
|
|
155
|
+
export function resolvedApprovalDecision(
|
|
156
|
+
requestId: string,
|
|
157
|
+
expectedSessionId: string | null | undefined,
|
|
158
|
+
expectedRegistration?: ApprovalRegistrationHandle,
|
|
159
|
+
): ApprovalDecision | null {
|
|
160
|
+
const outcome = resolvedApprovalOutcome(
|
|
161
|
+
requestId,
|
|
162
|
+
expectedSessionId,
|
|
163
|
+
expectedRegistration,
|
|
164
|
+
);
|
|
165
|
+
return outcome?.kind === "decision" ? outcome.decision : null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function markApprovalTerminal(
|
|
169
|
+
requestId: string,
|
|
170
|
+
state: ApprovalTerminalState,
|
|
171
|
+
options: {
|
|
172
|
+
decision?: ApprovalDecision["decision"] | null;
|
|
173
|
+
decidedBy?: string | null;
|
|
174
|
+
note?: string | null;
|
|
175
|
+
sessionId?: string | null;
|
|
176
|
+
resolvedAt?: number;
|
|
177
|
+
} = {},
|
|
178
|
+
now = Date.now(),
|
|
179
|
+
expectedRegistration?: ApprovalRegistrationHandle,
|
|
180
|
+
): ApprovalAcceptance {
|
|
181
|
+
const record = readRawRecord(requestId);
|
|
182
|
+
if (!record || !matchesRegistration(record, expectedRegistration)) {
|
|
183
|
+
return { matched: false, newlyResolved: false };
|
|
184
|
+
}
|
|
185
|
+
const requestSessionId = normalizedSessionId(record.request.sessionId);
|
|
186
|
+
if (requestSessionId !== normalizedSessionId(options.sessionId)) {
|
|
187
|
+
return { matched: false, newlyResolved: false };
|
|
188
|
+
}
|
|
189
|
+
const existingOutcome = recordOutcome(record);
|
|
190
|
+
if (existingOutcome) return acceptance(record, existingOutcome, false);
|
|
191
|
+
if (record.state !== "pending") return { matched: false, newlyResolved: false };
|
|
192
|
+
const resolved = terminalApproval(requestId, state, {
|
|
193
|
+
...options,
|
|
194
|
+
sessionId: requestSessionId,
|
|
195
|
+
resolvedAt: options.resolvedAt ?? now,
|
|
196
|
+
});
|
|
197
|
+
const outcome: ApprovalOutcome = { kind: "terminal", status: state, resolved };
|
|
198
|
+
const claim = claimApprovalWinner(record, outcome, now);
|
|
199
|
+
if (!claim.winner) return { matched: false, newlyResolved: false };
|
|
200
|
+
if (!claim.won) return acceptance(record, claim.winner.outcome, false);
|
|
201
|
+
return acceptance(record, claim.winner.outcome, true);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function pendingApprovalRegistrations(
|
|
205
|
+
now = Date.now(),
|
|
206
|
+
): PendingApprovalRegistration[] {
|
|
207
|
+
return recordFiles()
|
|
208
|
+
.map(readRecordPath)
|
|
209
|
+
.filter((record): record is ApprovalRecord =>
|
|
210
|
+
record != null &&
|
|
211
|
+
record.state === "pending" &&
|
|
212
|
+
now - record.updatedAt <= MAX_PENDING_AGE_MS &&
|
|
213
|
+
recordWinner(record) == null,
|
|
214
|
+
)
|
|
215
|
+
.sort((a, b) => b.updatedAt - a.updatedAt)
|
|
216
|
+
.slice(0, 100)
|
|
217
|
+
.map((record) => ({
|
|
218
|
+
request: record.request,
|
|
219
|
+
handle: registrationHandle(record),
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Compatibility wrapper for callers that only render pending requests. */
|
|
224
|
+
export function pendingApprovalRequests(now = Date.now()): ApprovalRequest[] {
|
|
225
|
+
return pendingApprovalRegistrations(now).map((item) => item.request);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function approvalScopeKey(request: Pick<ApprovalRequest, "requestId" | "sessionId">): string {
|
|
229
|
+
return `${request.requestId}\u0000${normalizedSessionId(request.sessionId) ?? ""}`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function coveredApprovalScopes(
|
|
233
|
+
pending: ApprovalRequest[],
|
|
234
|
+
now: number,
|
|
235
|
+
): Array<{ requestId: string; sessionId?: string }> {
|
|
236
|
+
const includedPending = new Set(pending.map(approvalScopeKey));
|
|
237
|
+
const covered = new Map<string, { requestId: string; sessionId?: string }>();
|
|
238
|
+
for (const path of recordFiles()) {
|
|
239
|
+
const record = readRecordPath(path);
|
|
240
|
+
if (!record) continue;
|
|
241
|
+
const winner = recordWinner(record);
|
|
242
|
+
if (now - (winner?.acceptedAt ?? record.updatedAt) > TERMINAL_RETENTION_MS) continue;
|
|
243
|
+
const isLivePending =
|
|
244
|
+
record.state === "pending" &&
|
|
245
|
+
winner == null &&
|
|
246
|
+
now - record.updatedAt <= MAX_PENDING_AGE_MS;
|
|
247
|
+
// Never claim an omitted live request as closed when the wire cap is hit.
|
|
248
|
+
if (isLivePending && !includedPending.has(approvalScopeKey(record.request))) continue;
|
|
249
|
+
const scope = {
|
|
250
|
+
requestId: record.request.requestId,
|
|
251
|
+
sessionId: normalizedSessionId(record.request.sessionId),
|
|
252
|
+
};
|
|
253
|
+
covered.set(approvalScopeKey(record.request), scope);
|
|
254
|
+
}
|
|
255
|
+
return [...covered.values()].slice(0, MAX_RECORDS);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
let lastStatusGeneratedAt = 0;
|
|
259
|
+
|
|
260
|
+
export function approvalsStatus(now = Date.now()): ApprovalsStatus {
|
|
261
|
+
pruneRecords(now);
|
|
262
|
+
const pending = pendingApprovalRequests(now);
|
|
263
|
+
const generatedAt = Math.max(Math.trunc(now), lastStatusGeneratedAt + 1);
|
|
264
|
+
lastStatusGeneratedAt = generatedAt;
|
|
265
|
+
return {
|
|
266
|
+
type: "approvals.status",
|
|
267
|
+
pending,
|
|
268
|
+
complete: false,
|
|
269
|
+
covered: coveredApprovalScopes(pending, now),
|
|
270
|
+
actions: pending.map(approvalAction),
|
|
271
|
+
generatedAt,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function resolvedFromDecision(
|
|
276
|
+
decision: ApprovalDecision,
|
|
277
|
+
request?: ApprovalRequest,
|
|
278
|
+
): ApprovalResolved {
|
|
279
|
+
return terminalApproval(decision.requestId, "applied", {
|
|
280
|
+
decision: decision.decision,
|
|
281
|
+
decidedBy: decision.decidedBy,
|
|
282
|
+
note: decision.note,
|
|
283
|
+
sessionId: request?.sessionId ?? decision.sessionId,
|
|
284
|
+
resolvedAt: decision.decidedAt,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function resolvedFromOutcome(
|
|
289
|
+
outcome: ApprovalOutcome,
|
|
290
|
+
request?: ApprovalRequest,
|
|
291
|
+
): ApprovalResolved {
|
|
292
|
+
return outcome.kind === "decision"
|
|
293
|
+
? resolvedFromDecision(outcome.decision, request)
|
|
294
|
+
: outcome.resolved;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export async function sendApprovalResolved(
|
|
298
|
+
client: RelayClient,
|
|
299
|
+
payload: ApprovalResolved,
|
|
300
|
+
): Promise<void> {
|
|
301
|
+
await client.send(payload, "phone", {
|
|
302
|
+
ttlMs: 15 * 60_000,
|
|
303
|
+
wake: false,
|
|
304
|
+
deliveryId: `approval-resolved-${requestDigest(payload.requestId)}`,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* replies with a matching ApprovalDecision (or the timeout fires — fail closed,
|
|
6
6
|
* i.e. deny). This is what makes a phone tap gate a real tool call.
|
|
7
7
|
*/
|
|
8
|
-
import type { PeerConfig } from "
|
|
9
|
-
import { RelayClient } from "
|
|
10
|
-
import type { ApprovalDecision, ApprovalRequest, Payload } from "
|
|
8
|
+
import type { PeerConfig } from "../../../../packages/core/relay-client";
|
|
9
|
+
import { RelayClient } from "../../../../packages/core/relay-client";
|
|
10
|
+
import type { ApprovalDecision, ApprovalRequest, Payload } from "../../../../packages/protocol/schema";
|
|
11
11
|
import {
|
|
12
12
|
acceptApprovalDecision,
|
|
13
13
|
markApprovalTerminal,
|
|
@@ -16,8 +16,9 @@ import {
|
|
|
16
16
|
resolvedFromOutcome,
|
|
17
17
|
sendApprovalResolved,
|
|
18
18
|
type ApprovalOutcome,
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
type ApprovalRegistrationHandle,
|
|
20
|
+
} from "./state";
|
|
21
|
+
import { primeSessionKeys, sendSessionPayload } from "../host/session-keys";
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Nobody answered — relay unreachable, or the phone never responded before the
|
|
@@ -77,7 +78,72 @@ export async function requestApproval(
|
|
|
77
78
|
};
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
|
-
const
|
|
81
|
+
const waiting = waitForDecision(client, req, timeoutMs, registrationHandle);
|
|
82
|
+
cancelDecisionWait = waiting.cancel;
|
|
83
|
+
await sendRequest(client, req, timeoutMs);
|
|
84
|
+
const decision = await waiting.promise;
|
|
85
|
+
|
|
86
|
+
if (decision) return decision;
|
|
87
|
+
// `expired`, not `system`: nobody denied anything — the phone was asleep,
|
|
88
|
+
// offline, or the app was killed. Hooks hand `expired` back to the agent's
|
|
89
|
+
// own permission prompt. Reporting it as a plain deny bricks the agent:
|
|
90
|
+
// every tool call stalls for the full timeout and then fails.
|
|
91
|
+
const expired = markApprovalTerminal(req.requestId, "expired", {
|
|
92
|
+
decision: "deny",
|
|
93
|
+
decidedBy: "expired",
|
|
94
|
+
note: "No response from phone before timeout",
|
|
95
|
+
sessionId: req.sessionId,
|
|
96
|
+
}, Date.now(), registrationHandle);
|
|
97
|
+
if (!expired.outcome) {
|
|
98
|
+
return {
|
|
99
|
+
...failClosed(req, "No response from phone before timeout"),
|
|
100
|
+
decidedBy: "expired",
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
await sendApprovalResolved(
|
|
104
|
+
client,
|
|
105
|
+
resolvedFromOutcome(expired.outcome, expired.request),
|
|
106
|
+
).catch(() => {});
|
|
107
|
+
return decisionFromOutcome(expired.outcome, req);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
// Transport blew up mid-flight (socket dropped, peer never attached). Like a
|
|
110
|
+
// failed connect this is `unreachable`: no human ever saw the request.
|
|
111
|
+
const expired = markApprovalTerminal(req.requestId, "expired", {
|
|
112
|
+
decision: "deny",
|
|
113
|
+
decidedBy: "unreachable",
|
|
114
|
+
note: "Approval channel error",
|
|
115
|
+
sessionId: req.sessionId,
|
|
116
|
+
}, Date.now(), registrationHandle);
|
|
117
|
+
if (!expired.outcome) {
|
|
118
|
+
return {
|
|
119
|
+
...failClosed(req, `Approval channel error: ${(err as Error).message}`),
|
|
120
|
+
decidedBy: "unreachable",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
await sendApprovalResolved(
|
|
124
|
+
client,
|
|
125
|
+
resolvedFromOutcome(expired.outcome, expired.request),
|
|
126
|
+
).catch(() => {});
|
|
127
|
+
return decisionFromOutcome(expired.outcome, req);
|
|
128
|
+
} finally {
|
|
129
|
+
cancelDecisionWait();
|
|
130
|
+
if (ownsClient) client.close();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function sendRequest(client: RelayClient, req: ApprovalRequest, timeoutMs: number): Promise<void> {
|
|
135
|
+
// The id carries no task content and lets APNs answer before iOS decrypts the card.
|
|
136
|
+
const options = { ttlMs: timeoutMs, wake: true, deliveryId: req.requestId };
|
|
137
|
+
if (req.sessionId) await sendSessionPayload(client, req, req.sessionId, "phone", options);
|
|
138
|
+
else await client.send(req, "phone", options);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function waitForDecision(
|
|
142
|
+
client: RelayClient, req: ApprovalRequest, timeoutMs: number,
|
|
143
|
+
registrationHandle?: ApprovalRegistrationHandle,
|
|
144
|
+
): { promise: Promise<ApprovalDecision | null>; cancel: () => void } {
|
|
145
|
+
let cancel = () => {};
|
|
146
|
+
const promise = new Promise<ApprovalDecision | null>((resolve) => {
|
|
81
147
|
let settled = false;
|
|
82
148
|
let durablePoll: NodeJS.Timeout | undefined;
|
|
83
149
|
let off = () => {};
|
|
@@ -89,7 +155,7 @@ export async function requestApproval(
|
|
|
89
155
|
off();
|
|
90
156
|
resolve(decision);
|
|
91
157
|
};
|
|
92
|
-
|
|
158
|
+
cancel = () => finish(null);
|
|
93
159
|
const timer = setTimeout(() => finish(null), timeoutMs);
|
|
94
160
|
// The long-running desktop monitor may receive and durably accept the
|
|
95
161
|
// same relay envelope first. Poll the shared registry so this one-shot
|
|
@@ -127,67 +193,7 @@ export async function requestApproval(
|
|
|
127
193
|
return terminalSent;
|
|
128
194
|
});
|
|
129
195
|
});
|
|
130
|
-
|
|
131
|
-
// opaque delivery id lets a generic APNs action answer the request even if
|
|
132
|
-
// iOS has not yet pulled and decrypted the full card.
|
|
133
|
-
const sendOptions = {
|
|
134
|
-
ttlMs: timeoutMs,
|
|
135
|
-
wake: true,
|
|
136
|
-
deliveryId: req.requestId,
|
|
137
|
-
};
|
|
138
|
-
if (req.sessionId) {
|
|
139
|
-
await sendSessionPayload(client, req, req.sessionId, "phone", sendOptions);
|
|
140
|
-
} else {
|
|
141
|
-
await client.send(req, "phone", sendOptions);
|
|
142
|
-
}
|
|
143
|
-
const decision = await decisionP;
|
|
144
|
-
|
|
145
|
-
if (decision) return decision;
|
|
146
|
-
// `expired`, not `system`: nobody denied anything — the phone was asleep,
|
|
147
|
-
// offline, or the app was killed. Hooks hand `expired` back to the agent's
|
|
148
|
-
// own permission prompt. Reporting it as a plain deny bricks the agent:
|
|
149
|
-
// every tool call stalls for the full timeout and then fails.
|
|
150
|
-
const expired = markApprovalTerminal(req.requestId, "expired", {
|
|
151
|
-
decision: "deny",
|
|
152
|
-
decidedBy: "expired",
|
|
153
|
-
note: "No response from phone before timeout",
|
|
154
|
-
sessionId: req.sessionId,
|
|
155
|
-
}, Date.now(), registrationHandle);
|
|
156
|
-
if (!expired.outcome) {
|
|
157
|
-
return {
|
|
158
|
-
...failClosed(req, "No response from phone before timeout"),
|
|
159
|
-
decidedBy: "expired",
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
await sendApprovalResolved(
|
|
163
|
-
client,
|
|
164
|
-
resolvedFromOutcome(expired.outcome, expired.request),
|
|
165
|
-
).catch(() => {});
|
|
166
|
-
return decisionFromOutcome(expired.outcome, req);
|
|
167
|
-
} catch (err) {
|
|
168
|
-
// Transport blew up mid-flight (socket dropped, peer never attached). Like a
|
|
169
|
-
// failed connect this is `unreachable`: no human ever saw the request.
|
|
170
|
-
const expired = markApprovalTerminal(req.requestId, "expired", {
|
|
171
|
-
decision: "deny",
|
|
172
|
-
decidedBy: "unreachable",
|
|
173
|
-
note: "Approval channel error",
|
|
174
|
-
sessionId: req.sessionId,
|
|
175
|
-
}, Date.now(), registrationHandle);
|
|
176
|
-
if (!expired.outcome) {
|
|
177
|
-
return {
|
|
178
|
-
...failClosed(req, `Approval channel error: ${(err as Error).message}`),
|
|
179
|
-
decidedBy: "unreachable",
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
await sendApprovalResolved(
|
|
183
|
-
client,
|
|
184
|
-
resolvedFromOutcome(expired.outcome, expired.request),
|
|
185
|
-
).catch(() => {});
|
|
186
|
-
return decisionFromOutcome(expired.outcome, req);
|
|
187
|
-
} finally {
|
|
188
|
-
cancelDecisionWait();
|
|
189
|
-
if (ownsClient) client.close();
|
|
190
|
-
}
|
|
196
|
+
return { promise, cancel };
|
|
191
197
|
}
|
|
192
198
|
|
|
193
199
|
function decisionFromOutcome(
|