granttap-mcp 0.8.18 → 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 +24 -12
- 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} +8 -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} +6 -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 +132 -9
- 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 +5 -0
- package/apps/bridge/src/engine/{invocation-decision-parse.ts → invocation/decision-parse.ts} +1 -1
- package/apps/bridge/src/engine/{invocation-ingest.ts → invocation/ingest.ts} +33 -21
- package/apps/bridge/src/engine/{invocation-log-reader.ts → invocation/log-reader.ts} +4 -3
- package/apps/bridge/src/engine/{invocation-parse.ts → invocation/parse.ts} +2 -2
- package/apps/bridge/src/engine/{invocation-query.ts → invocation/query.ts} +9 -9
- package/apps/bridge/src/engine/{invocation-scope.ts → invocation/scope.ts} +5 -5
- package/apps/bridge/src/engine/{engine-declaration.ts → protocol/engine-declaration.ts} +2 -2
- 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} +6 -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} +18 -15
- 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} +93 -92
- 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} +4 -4
- 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} +50 -22
- package/apps/bridge/src/mesh/snapshot/merge.ts +51 -0
- package/apps/bridge/src/mesh/{scoped-view.ts → snapshot/scoped-view.ts} +30 -6
- 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.ts → store/index.ts} +71 -351
- 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-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 +8 -5
- package/apps/bridge/src/policy/codex-project-ask.ts +1 -1
- package/apps/bridge/src/policy/decision-log.ts +1 -1
- package/apps/bridge/src/policy/effective-action.ts +61 -12
- 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 +35 -30
- 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/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 -162
- package/packages/protocol/messages/mesh/index.ts +5 -0
- package/packages/protocol/messages/{mesh-invocations.ts → mesh/invocations.ts} +1 -1
- 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 +15 -2
- 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 -275
- package/apps/bridge/src/install.ts +0 -817
- package/apps/bridge/src/mesh/runtime-handoff.ts +0 -186
- package/apps/bridge/src/mesh/runtime.ts +0 -205
- package/apps/bridge/src/mesh/snapshot-merge.ts +0 -32
- package/apps/bridge/src/mesh/store-state.ts +0 -292
- package/apps/bridge/src/monitor.ts +0 -793
- package/apps/bridge/src/pairing.ts +0 -136
- package/apps/bridge/src/sessions/claude.ts +0 -618
- package/apps/bridge/src/sessions/codex.ts +0 -781
- 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 -137
- 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-invocation-protocol.ts → protocol/engine-invocation-protocol.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/mesh/{store-support.ts → store/support.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
|
@@ -185,6 +185,14 @@ and from then on that computer takes part in the mesh like any other: its
|
|
|
185
185
|
chats are the Project's, its claims are seen by every other computer, and a
|
|
186
186
|
Task can be handed to it.
|
|
187
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
|
+
|
|
188
196
|
### A claim released by the person
|
|
189
197
|
|
|
190
198
|
A claim outlives an agent that crashed or was closed, and the files it names
|
|
@@ -247,16 +255,20 @@ grok plugin install granttap --trust
|
|
|
247
255
|
```
|
|
248
256
|
|
|
249
257
|
For Cursor, use the reviewed **GrantTap** Marketplace listing when it is visible
|
|
250
|
-
to your account
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
plugin
|
|
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
|
|
260
272
|
[`plugins/granttap/README.md`](plugins/granttap/README.md).
|
|
261
273
|
|
|
262
274
|
For the full background helper and provider hooks, install the CLI and run
|
|
@@ -268,8 +280,8 @@ granttap setup
|
|
|
268
280
|
```
|
|
269
281
|
|
|
270
282
|
`granttap setup` detects supported local agents, installs or repairs their
|
|
271
|
-
hooks, installs the background helper,
|
|
272
|
-
|
|
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
|
|
273
285
|
interactive terminal. It ends with one exact next action.
|
|
274
286
|
|
|
275
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(
|