资讯详情

Long Horizon 项目实战:用 `gws` CLI 技能打通 Google Workspace 全场景读写

📅 2026/9/16 10:43:26 | 华诺云谱 👁 阅读
Long Horizon 项目实战:用 `gws` CLI 技能打通 Google Workspace 全场景读写
Long Horizon 项目实战用gwsCLI 技能打通 Google Workspace 全场景读写【免费下载链接】adk-samplesA collection of sample agents built with Agent Development Kit (ADK)项目地址: https://gitcode.com/GitHub_Trending/ad/adk-samples导读本文围绕 Long Horizoncore/python/long-horizon-harness基于 ADK 与 Google Agent Platform 的 Agent Harness 参考实现内置的google-workspace使用技能展开完整讲解如何通过社区 CLIgwsGoogle Workspace CLI版本锁定 0.22.x读写 Drive、Docs、Sheets、Gmail、Calendar、Chat、Tasks、Slides 等十余个 Workspace 服务。你将掌握先探测预注入令牌、再配置 OAuth 客户端/服务账号的分层鉴权策略沙箱内 headless 环境下绕开浏览器重定向的 loopback 桥接技巧以及从命令形态、参数自省gws schema到常见故障排错的完整实战链路。技能文件本体位于 core/python/long-horizon-harness/horizon/builtin_skills/google-workspace/SKILL.md本文以它为骨架并佐以 Long Horizon 仓库内的源码与测试证据。技能定位一个命令覆盖全部 Workspace 面gws安装npm install -g googleworkspace/cli几乎覆盖用户可能问到的每一个 Workspace API——Drive、Docs、Sheets、Gmail、Calendar、Chat、Tasks、Slides、Keep、Forms、Apps Script、Meet。它是一个社区 CLI非 Google 官方支持产品其价值在于封装了 REST API让 Agent 不必手写curl但它不内置任何凭据——你必须先配置 OAuth 客户端或服务账号再完成认证。技能的前置 YAML 元数据说明了触发条件与依赖--- name: google-workspace description: Read/write Google Drive, Docs, Sheets, Gmail, Calendar, Chat, Tasks, Slides via the gws CLI. Needs an OAuth client or service account configured first. Use for any Google Workspace task. ---该技能由仓库单元测试守护tests/unit/test_builtin_google_workspace_skill.py 中明确写到技能必须引导 Agent先尝试预注入的GOOGLE_WORKSPACE_CLI_TOKEN再进入gws auth login的 loopback 登录流程——这正是之前你能看到我的邮件吗这类请求在令牌已存在且可用时仍触发完整登录流程的回归教训。测试还断言了技能文件能被load_skill_from_dir正常解析、frontmatter 名称与描述存在并且GOOGLE_WORKSPACE_CLI_TOKEN在文中出现的位置必须早于gws auth logintest_google_workspace_puts_token_before_loopback_login。何时启用该技能任何点名 Google Workspace 面surface的用户请求都适用读取这个 Drive 文件、列出我的 Drive 文件夹、分享这个文档创建一个包含……的 Google Doc、更新这个表格、追加一行发一封邮件、起草 Gmail 回复、查一下收件箱里有没有……我日历上有什么安排、安排一个会议发到这个 Chat 空间不要对原始 Workspace REST API 直接curl——gws已经封装了它们并处理了分页、重试与 JSON 解析。鉴权四条路径与严格的优先级第一优先先探测预注入令牌在任何 OAuth 客户端、服务账号或登录动作之前先检查预注入的访问令牌。当用户在 Web UI 中使用了Connect WorkspaceGOOGLE_WORKSPACE_CLI_TOKEN密钥会被自动注入到每一条bash命令的环境里它是gws最高优先级的鉴权来源——不需要 OAuth 客户端、不需要登录、不需要 loopback 桥接。正确的姿势是不要问、不要检查环境变量直接对你需要的面做一次廉价读取探测bash(commandexport GOOGLE_WORKSPACE_CLI_CONFIG_DIR/workspace/lha/config/gws GOOGLE_WORKSPACE_CLI_KEYRING_BACKENDfile gws gmail messages list --params {\maxResults\: 1})若返回数据说明令牌可用直接继续任务。若返回403 / scope 错误说明令牌有效但用户未授予该面或只有只读。Connect Workspace 流程是按面drive / gmail / calendar / sheets / docs / chat / tasks / slides / keep / script / meet / forms× 只读|读写 分开授权的默认只读——此时应请用户重新连接并勾选所需的面或读写权限而不是启动登录流程。若返回401 / not logged in说明令牌确实缺失或已过期约 1 小时有效、无刷新机制——这时才回退到下面的 OAuth 客户端登录或请用户重新点击 Connect Workspace。不要信任gws auth status在环境变量令牌模式下它报告的是auth_method: none/credential_source: token_env_var即使读取成功也是如此——真正的读取才是唯一可靠的检查。跳过探测直接gws auth login是典型的错误它会把用户拖入一个已有令牌完全不需要的浏览器重定向桥接流程。其次配置 OAuth 客户端gws没有内置 OAuth 客户端gws auth login只有在下列条件之一就位后才会成功client_secret.json——把 OAuth 客户端密钥文件放入 gws 配置目录。沙箱中的首选方案见下文创建 OAuth 客户端。保持该目录在/workspace上见下一节以便升级后存活。环境变量——设置GOOGLE_WORKSPACE_CLI_CLIENT_ID与GOOGLE_WORKSPACE_CLI_CLIENT_SECRET。服务账号——设置GOOGLE_APPLICATION_CREDENTIALS/path/to/key.json。此模式下无需gws auth login调用直接用密钥鉴权适用于完全无人值守的自动化、无用户在场。gws auth setup——为你自动配置 GCP 项目 OAuth 客户端但它渲染的是一个全屏交互式 TUI无法从bash驱动没有键盘输入。不要 headless 使用它——请手工创建client_secret.json选项 1。创建 OAuth 客户端一次性通过gcloud或 Cloud console 自建 OAuth 客户端时选择Desktop app客户端类型不要选Web application。gws auth login监听一个随机 loopback 端口Web 应用客户端会以Error 400: redirect_uri_mismatch拒绝。Desktop 应用客户端接受 loopback。对于企业版 Workspace 组织把同意屏幕受众设为Internal。Internal 受众客户端可以绕过默认 gcloud OAuth 客户端会撞上的 Context-Aware Access / Account restricted 拦截。client_secret.json的project_id必须是认证账号可用的项目——否则调用会以配额项目 403 失败见故障处理。配置目录与钥匙串沙箱陷阱每条gws命令都要设置下面两个环境变量——每条bash调用都是全新的非登录 shellexport 不会在调用之间传递export GOOGLE_WORKSPACE_CLI_CONFIG_DIR/workspace/lha/config/gws export GOOGLE_WORKSPACE_CLI_KEYRING_BACKENDfileGOOGLE_WORKSPACE_CLI_CONFIG_DIR——把配置目录存放client_secret.json、令牌和加密密钥重定位到/workspace。默认的~/.config/gws在运行时升级时不会迁移不设置的话下次升级后你得重做一次浏览器登录。GOOGLE_WORKSPACE_CLI_KEYRING_BACKENDfile——headless 环境没有操作系统钥匙串file 后端把密钥写进配置目录因此也在/workspace上。这套二进制在$HOME、凭据在/workspace的持久化模型在 bootstrap-google-tools/SKILL.md 中有更完整的说明运行时镜像升级后沙箱会被重新供应只有/workspace会被迁移且是 zip 迁移会丢弃符号链接、剥离可执行位所以 CLI 二进制安装到~/.local~/.local/bin已在 PATH 上而 OAuth 客户端 令牌等昂贵凭据必须指向/workspace/lha/config/。认证命令OAuth 客户端配置好后用以下命令认证bash(commandgws auth login --readonly) # 只读 scope从这里开始 bash(commandgws auth login) # 全部默认 scope bash(commandgws auth login -s drive,gmail,sheets) # 限制选择器 bash(commandgws auth login --scopes comma,separated,scopes)从只读 scope 开始--readonly——最小权限原则。只有当任务确实需要写入时才为特定面重跑gws auth login --scopes …增加写 scopeGoogle 的增量同意incremental consent会把新 scope 与已授权内容合并因此你永远不会一开始就过度授权。gws auth login仅支持 loopback 浏览器模式——它打开浏览器并在本地监听 OAuth 重定向。没有device-code / 粘贴代码的标志。headless 下完成 loopback 流程它在沙箱里确实可用——你需要手工桥接重定向。浏览器在用户的机器上打开但监听器在容器内部所以要把重定向 URL 转接过去。URL 在一个回合出现、用户在后一个回合粘贴回来因此使用跨回合进程管道process(actionspawn, ...)processprocess(actionspawn, commandexport GOOGLE_WORKSPACE_CLI_CONFIG_DIR/workspace/lha/config/gws GOOGLE_WORKSPACE_CLI_KEYRING_BACKENDfile gws auth login) # 读取打印的 auth URL原样呈现给用户然后等待 process(actionpoll, session_idid)用户打开认证 URL、同意授权浏览器会落到一个http://localhost:port/?code...scope...页面——这个页面加载不出来监听器在沙箱里不在用户机器上。请用户从地址栏复制完整重定向 URL并粘贴回来。在沙箱内对该 URL 执行curl把 code 交给等待中的监听器完成交换并写入令牌bash(commandcurl -s http://localhost:port/?code...scope...)后台的gws auth login随即完成。localhost在 Layer A 的允许列表上所以这条curl不会被门禁拦截。对于完全无人值守、没有用户在场粘贴重定向的任务请改用服务账号——loopback 桥接必须有活着的用户。任何时候都可以用bash(commandgws auth status)检查状态。值得一提的是Long Horizon 的 Web UI 提供了完整的Connect Google体验quickstart.md 第 6 节说明你只需自备 OAuth 客户端并设置三个环境变量服务端逻辑已内置于 horizon/auth/oauth.pyLHA_GCP_OAUTH_CLIENT_IDclient-id LHA_GCP_OAUTH_CLIENT_SECRETclient-secret # 同时作为签名 state 的 HMAC 密钥 LHA_GCP_OAUTH_REDIRECT_URIhttp://localhost:3000/lha/gcp/callbackoauth.py中GWS_TOKEN_KEY GOOGLE_WORKSPACE_CLI_TOKEN正是预注入令牌的落点Workspace 连接令牌作为普通按用户密钥存入 Secret Manager经secret_env()注入沙箱命令环境模型只看到密钥名、永远看不到值。该文件里的WORKSPACE_SURFACES映射则定义了每个面在只读/读写模式下的 scope 组合例如 Chat 只读 chat.messages.readonlychat.spaces.readonly两个 scopeMeet 的写能力用meetings.space.createdworkspace_scopes()负责按面与读写开关组装 scope 列表——这从源码层面印证了 SKILL.md 中Connect Workspace 按面 × 只读|读写授权的说法。命令形态与参数自省通用形态gws service resource [sub-resource] method [flags]查询参数以JSON形式通过--params传入请求体通过--json传入。两者都用单引号包裹让 shell 保留内部双引号gws drive files list --params {pageSize: 5, q: trashedfalse} gws drive files create --json {name: notes.txt} --upload notes.txt若干常见操作还有更符合人体工学的辅助命令以为前缀接受普通 flag 而非 JSON——例如gws sheets read、gws sheets append、gws gmail send、gws drive upload。常用全局 flag--format json|table|yaml|csv默认 json、--dry-run只校验不打 API、--page-all自动分页输出 NDJSON、-o/--output path保存二进制响应、--sanitize template用 Model Armor 过滤响应。发现不要猜参数bash(commandgws drive --help) # 资源 方法 bash(commandgws schema drive.files.list) # 参数、类型、默认值gws schema service.resource.method打印某个方法精确的参数形状。每当不确定某个 flag 时就运行它或gws service --help——它对已安装版本是权威的。深度场景优先官方分面技能下表只是速查不是完整面。gws上游提供了维护中的分面技能——gws-drive、gws-gmail、gws-sheets、gws-calendar、gws-chat、gws-people、gws-slides、gws-tasks以及精选 recipes——覆盖本文件未涉及的较冷门方法。超出常见操作时把相关技能安装进工作区并阅读它注意命令打印的路径bash(commandnpx -y skills add https://github.com/googleworkspace/cli/tree/main/skills/gws-drive)把gws-drive换成你需要的面即可。这些是版本锁定的文档所以当技能与已安装二进制不一致时gws schema service.resource.method胜出——它反映的是 PATH 上真实的 gws。bootstrap-google-tools/SKILL.md 还给出了另一种安装方式npx --yes skills add googleworkspace/cligws-shared -y技能会被暂存到.agents/skills/skill/Long Horizon 会自动发现只需load_skill(actionreload)即可热加载。常见操作速查表你想做…命令列出 Drive 文件gws drive files list --params {q: trashedfalse, pageSize: 20}获取 Drive 文件元数据gws drive files get --params {fileId: ID, fields: id,name,mimeType}下载文件内容gws drive files get --params {fileId: ID, alt: media} -o out.bin导出 Doc/Sheetgws drive files export --params {fileId: ID, mimeType: text/plain} -o out.txt读取 Sheet 区域gws sheets read --spreadsheet ID --range Sheet1!A1:D10追加 Sheet 行gws sheets append --spreadsheet ID --range Sheet1 --json {values: [[a,b]]}列出 Gmail 消息gws gmail messages list --params {q: from:aliceexample.com newer_than:7d, maxResults: 10}获取单条 Gmail 消息gws gmail messages get --params {id: MSG_ID, format: full}发送邮件gws gmail send --to aliceexample.com --subject Hi --body Hello!列出 Calendar 事件gws calendar events list --params {calendarId: primary, maxResults: 10}列出 Tasks 任务列表gws tasks tasklists list列出某列表中的任务gws tasks tasks list --params {tasklist: TASKLIST_ID}添加任务gws tasks tasks insert --params {tasklist: TASKLIST_ID} --json {title: Buy milk}读取演示文稿gws slides presentations get --params {presentationId: ID}列出 Keep 笔记gws keep notes list获取 Form 及其回复gws forms forms get --params {formId: ID}·gws forms forms responses list --params {formId: ID}在写/删调用前务必用gws schema …确认精确参数——上面是常见情况不是穷尽契约。Keep 的注意事项Google Keep API 仅限 Workspace Enterprise 域通过服务账号的域级授权domain-wide delegation使用——普通的 Connect-Workspace 访问令牌无论授予什么 scopegws keep …大概率都会 403。如果发生告诉用户这是 API 限制而不是授权缺失。routine 下的注意只有在 routine 的secrets:中声明了密钥名GOOGLE_WORKSPACE_CLI_TOKEN时Google 令牌才存在。routines/SKILL.md 说明 routine 运行在独立于用户工作区的全新沙箱lhart-id中只持有其声明的 secrets——secrets:列表本身就是爆炸半径边界。Google Chat未读状态与发送者姓名两个 Chat 限制受 scope 约束答应结果前要先检查未读消息需要额外 scope但gws有原生命令。默认读 scopechat.spaces.readonlychat.messages.readonly能列出空间、读取消息但不暴露每空间的未读状态——阅读位置需要chat.users.readstate.readonly。有了该 scope 后不要手写 RESTgws把 read-state 端点封装为gws chat users spaces getSpaceReadState。每空间未读状态需自行计算——没有单一的列出未读调用列出你关心的空间DM/群聊按lastActiveTime排序gws chat spaces list --params {pageSize: 1000}对每个空间读取你的最后阅读标记与最新消息并比较gws chat users spaces getSpaceReadState --params {name: users/me/spaces/space/spaceReadState} gws chat spaces messages list --params {parent: spaces/space, pageSize: 1, orderBy: create_time DESC}当最新消息的createTime严格晚于 read state 的lastReadTime且该消息的sender.name形如users/id不是你自己时该空间为未读。orderBy是create_time DESC——snake_case 字段 ASC/DESC不是createTime desc。解析你自己的users/id以跳过自己发的消息是唯一的难点Connect-Workspace 令牌下people/me会 403所以要显式传入你的 id从任意你发过的消息里读出来或执行一次gws people people get不要依赖me。用gws people people getBatchGet解析其余发送者 id 为姓名见下面的 People 说明。没有 read-state scope就得不到真正的未读——要如实告知然后要么重新认证加上它gws auth login --scopes https://www.googleapis.com/auth/chat.users.readstate.readonly增量同意会合并要么退而求其次用新近度作为代理按lastActiveTime列空间、拉取每个空间的最新消息。要说明你用的是哪种。发送者返回的是用户 ID 而非姓名。Chat 把每个发送者返回为users/numeric-idChat 读 scope 不会把这些解析成显示名。People API 可以gws封装为gws people …而 Connect Workspace 现在把该授权作为独立的Directory面提供scopedirectory.readonly——无论读写开关如何都只读。把 ID 变成姓名如果gws people读取 403说明用户没勾选Directory——请他们重新连接时勾上。批量解析——一个 Chat 线程有大量不同发送者所以把users/numeric-id里的所有numeric-id收集起来用一次getBatchGet调用解析而不是每个 ID 一次getPeople 资源 id 就是numeric-idgws people people getBatchGet --params {resourceNames: [people/id1, people/id2], personFields: names,emailAddresses}从responses[].person.names[0].displayName读取每个姓名。仅在一次性查找时使用单 ID 的gws people people get --params {resourceName: people/numeric-id, personFields: names}。如果某发送者无法被识别为域成员回退到gws people people searchDirectoryPeople --params {query: name-or-email, readMask: names,emailAddresses}。依赖它们之前用gws schema people.people.get确认精确参数形状。故障处理速查症状处理No OAuth client configured先配置凭据见鉴权放入client_secret.json、设置GOOGLE_WORKSPACE_CLI_CLIENT_*环境变量或使用服务账号gws auth login卡在监听器上headless 下是预期行为转接 loopback——中继重定向 URL 并curl回去见headless 下完成 loopback 流程。仅在没有用户在场粘贴重定向时回退到服务账号登录令牌不持久 / no keyring 错误或运行时升级后被要求重新登录在每条gws调用上设置GOOGLE_WORKSPACE_CLI_CONFIG_DIR/workspace/lha/config/gws与GOOGLE_WORKSPACE_CLI_KEYRING_BACKENDfile配置 密钥因此位于可持久化的/workspace~/.config/gws不会持久登录时Error 400: redirect_uri_mismatchOAuth 客户端是 Web application 类型重建为Desktop apploopback 端口同意时access_denied/ Account restrictedOAuth 客户端上的 Context-Aware Access。使用Internal受众客户端见创建 OAuth 客户端401 /not logged in若鉴权源是GOOGLE_WORKSPACE_CLI_TOKEN约 1 小时的令牌已过期——请用户重新点击 Connect Workspace它没有刷新机制。否则是 OAuth 登录过期重跑gws auth logingws auth status显示auth_method: none但读取正常环境变量令牌GOOGLE_WORKSPACE_CLI_TOKEN作为鉴权源时符合预期auth status不反映它。用一次真实读取确认而不是auth status403scope 太窄已授权 scope 不覆盖该调用。若鉴权源是GOOGLE_WORKSPACE_CLI_TOKEN环境变量令牌说明 Connect Workspace 授权是按面且默认只读的——请用户重新连接并包含所需的面或读写权限。在 OAuth 登录路径上则用所需 scope 重新认证gws auth login --scopes …Google 的增量同意会与已授权内容合并403serviceUsageConsumer/ 配额项目与 scope 403 不同client_secret.json的project_id是账号无法结算的项目。把客户端指向账号可用的项目账号需要其上的roles/serviceusage.serviceUsageConsumergws: command not found未安装。npm install -g googleworkspace/cli。沙箱内还需先装 NodeDebian x86_64 无xz须下载.tar.gz而非.tar.xz安装到~/.local并ln -sf到~/.local/bin详见 bootstrap-google-tools/SKILL.md不确定某个 flag 或参数gws service --help或gws schema service.resource.method。不要猜与 Long Horizon 安全模型的衔接gws调用并非游离于沙箱安全体系之外。permission-model.md 说明默认姿态下 shell 直接运行只有真正危险的操做才弹窗确认其中明确把gws … delete归入破坏性删除类与bq rm、gcloud … delete、terraform destroy同级会被command_safety分类器标记为需要用户确认而gws … send这类写操作在动词级分类器不做 SQL/语义解析的前提下按普通 shell 命令放行。另外oauth.py 中GWS_META_KEYGOOGLE_WORKSPACE_SCOPES_META记录每个用户已授权的{surfaces: [...], readonly: bool}供/status展示授权范围——这与 SKILL.md 中按面 × 只读|读写、默认只读的授权模型完全对应。小结google-workspace技能为 Long Horizon 中的 Agent 提供了一条从探测预注入令牌到OAuth 客户端/服务账号兜底的完整 Workspace 鉴权阶梯配合gws schema参数自省、前缀辅助命令和分面官方技能几乎覆盖所有日常 Workspace 读写场景。核心要诀有三令牌优先、最小 scope、凭据落/workspace——前者避免无谓的登录桥接中间者守住最小权限边界后者保证沙箱升级后登录状态不丢。技能由 test_builtin_google_workspace_skill.py 持续守护确保令牌优先于 loopback 登录的路径不会在后续演进中回归。【免费下载链接】adk-samplesA collection of sample agents built with Agent Development Kit (ADK)项目地址: https://gitcode.com/GitHub_Trending/ad/adk-samples创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。