{"_id":"@buddhilive/dsh-authorization","name":"@buddhilive/dsh-authorization","dist-tags":{"alpha":"0.1.2-alpha.3","latest":"0.1.2-alpha.3"},"versions":{"0.1.2-alpha.3":{"name":"@buddhilive/dsh-authorization","description":"Authorization seam (ctx.authorization): plugin-owned flows that obtain a credential through a conversation with the human","version":"0.1.2-alpha.3","publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/Buddhilive/buddhi-ai-harness.git","directory":"packages/credentials/authorization"},"type":"module","main":"lib/index.js","types":"lib/types/index.d.ts","exports":{".":{"types":"./lib/types/index.d.ts","default":"./lib/index.js"},"./invariant":{"types":"./lib/types/invariant.d.ts","default":"./lib/invariant.js"},"./types":{"types":"./lib/types/types.d.ts","default":"./lib/types/types.js"},"./src/*":"./src/*","./package.json":"./package.json"},"license":"MIT","peerDependencies":{"@buddhilive/dsh-credentials":"^0.1.2-alpha.3","@buddhilive/dsh-invariants":"^0.1.2-alpha.3","@buddhilive/dsh-llm":"^0.1.2-alpha.3","@deepseek-ai/cordis":"^4.0.2"},"devDependencies":{"@buddhilive/dsh-credentials":"^0.1.2-alpha.3","@buddhilive/dsh-invariants":"^0.1.2-alpha.3","@buddhilive/dsh-llm":"^0.1.2-alpha.3","@deepseek-ai/cordis":"^4.0.2"},"_id":"@buddhilive/dsh-authorization@0.1.2-alpha.3","bugs":{"url":"https://github.com/Buddhilive/buddhi-ai-harness/issues"},"homepage":"https://github.com/Buddhilive/buddhi-ai-harness#readme","_integrity":"sha512-bsjc1DrHdqVbzAseGhUiRR73gG3FqR7Td/FyH+NUPfZu7y0ISpmWyLs9qcqop+sajxVY1PCpRjpCX4srsyyZug==","_resolved":"C:\\DevDojo\\Buddhi\\buddhi-ai-harness\\dist\\npm\\buddhilive-dsh-authorization-0.1.2-alpha.3.tgz","_from":"file:C:/DevDojo/Buddhi/buddhi-ai-harness/dist/npm/buddhilive-dsh-authorization-0.1.2-alpha.3.tgz","_nodeVersion":"24.13.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-bsjc1DrHdqVbzAseGhUiRR73gG3FqR7Td/FyH+NUPfZu7y0ISpmWyLs9qcqop+sajxVY1PCpRjpCX4srsyyZug==","shasum":"a356c1abc6f20e865aee645ce0f7663480f8c920","tarball":"https://registry.npmjs.org/@buddhilive/dsh-authorization/-/dsh-authorization-0.1.2-alpha.3.tgz","fileCount":13,"unpackedSize":70473,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCEUGSByQG/NNZ/awq2SiQ2ccdY0LYOdf7Y9c6wnhuFSAIgPm21EkD0DCAPfZZWd6qr2pZSDaTspzZ2Vp072NJLmP8="}]},"_npmUser":{"name":"buddhilive","email":"visitbudkavin@gmail.com"},"directories":{},"maintainers":[{"name":"buddhilive","email":"visitbudkavin@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/dsh-authorization_0.1.2-alpha.3_1788165527391_0.6417439670323715"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-31T08:38:47.252Z","0.1.2-alpha.3":"2026-08-31T08:38:47.518Z","modified":"2026-08-31T08:38:47.719Z"},"maintainers":[{"name":"buddhilive","email":"visitbudkavin@gmail.com"}],"description":"Authorization seam (ctx.authorization): plugin-owned flows that obtain a credential through a conversation with the human","homepage":"https://github.com/Buddhilive/buddhi-ai-harness#readme","repository":{"type":"git","url":"git+https://github.com/Buddhilive/buddhi-ai-harness.git","directory":"packages/credentials/authorization"},"bugs":{"url":"https://github.com/Buddhilive/buddhi-ai-harness/issues"},"license":"MIT","readme":"---\ndescription: \"面向用户与维护者的授权 flow 注册表：获取配置无法提供的凭据，因为拿到它需要与人对话。\"\nkind: \"package-reference\"\n---\n\n# @buddhilive/dsh-authorization\n\n[English](README.md) | 中文\n\n## 概述\n\n`dsh-authorization` 通过询问人来获取配置无法提供的凭据：插件为每个凭据注册一个 flow，配置 UI 或其他界面发起一次尝试，其 notice 与提问恰好抵达发出请求的那个页面。人用 flow 提供的方法之一登录、粘贴一个码或回答一个问题；flow 结束时，其凭据记录已提交到 `dsh-credentials` 存储，而只有观察到这次提交时，尝试才报告 `authorized`。拒绝或撤销的尝试以 `cancelled` 结算而非报错，因此界面能区分「人说了不」与「flow 出了故障」。当凭据必须交互式获取时选择它：它建立在凭据 seam 的记录半侧之上、需要挂载该存储，且本身不随附任何 flow——由你的插件注册。\n\n## 目录\n\n- [使用本包](#use-this-package)\n- [理解实现](#understand-the-implementation)\n- [进一步探索](#further-exploration)\n- [模型体验](#model-experience)\n- [已知限制与延期工作](#known-limitations-and-deferred-work)\n- [开发备注](#dev-note)\n\n-----\n\n<a id=\"use-this-package\"></a>\n## 使用本包\n\n本包是产品中负责获取「必须由人交出来」的凭据的部分：插件注册一个知道如何取得自己那份凭据的 flow，任何界面都能发起尝试并向人展示该做什么。常用路径是显式的——为你插件持有的每个凭据注册一个 flow，然后从人正看着的那个界面发起尝试。\n\n### 何时使用\n\n只要凭据只能通过与人对话获得——OAuth 式登录、一次性码、选一个账号——且无法存入配置，就使用它。如果凭据是部署方可以提供的一个固定密钥，请改用凭据 seam 存储它。无头或 ACP 组合也可以安全挂载本包：它本身不提供任何 flow，因此除非插件注册了 flow，否则不会要求人登录。\n\n### 注册 flow\n\n你的插件为它持有的每个凭据声明一个 flow，以该 flow 写入的 `<scope>/<id>` 凭据记录为键——scope 点名你的插件，id 点名它拥有的一条凭据：\n\n```ts\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { AuthorizationSession } from '@buddhilive/dsh-authorization'\nimport { credentialKey } from '@buddhilive/dsh-credentials'\n\ndeclare const ctx: Context\ndeclare const exchangeCode: (code: string, signal: AbortSignal) => Promise<{ token: string }>\n\nconst key = credentialKey('llm-pi-ai', 'openai-codex') // <scope>/<id> — your plugin / this credential\n\nconst dispose = ctx.authorization.registerFlow({\n  key,\n  label: 'ChatGPT (Codex)',\n  methods: [{ id: 'oauth', label: 'Sign in with ChatGPT' }, { id: 'api-key', label: 'Paste a key' }],\n  async run(session: AuthorizationSession) {\n    session.notify({ message: 'Continue in your browser', url: 'https://auth.example/start' })\n    const code = await session.prompt({ kind: 'text', message: 'Paste the code' })\n    const { token } = await exchangeCode(code, session.signal)\n    await ctx.credentials.modifyRecord(key, () => Promise.resolve({ kind: 'grant', payload: { token } }))\n  },\n})\n\nctx.authorization.list()          // every registered flow, with inFlight\nctx.authorization.describe(key)   // the entry above, or undefined\ndispose()                         // unregister; withdraws any running attempt\n```\n\nflow 声明它写入的凭据记录、面向用户的标签以及它提供的登录方法，最优先者在前。`run()` 通过 session 与人对话——单向 notice 与 flow 无法自行回答的问题——并且必须在返回前通过 `ctx.credentials` 提交记录：seam 会拒绝未提交就返回的 flow。`list()` 与 `describe()` 让界面展示可授权的内容以及是否有尝试在运行；`dispose()` 注销该 flow 并撤销仍在运行中的尝试。\n\n### 发起一次尝试\n\n每个凭据同时只允许一次尝试。交互随请求传入而非存放在注册表中，因此提问恰好抵达发问的那个页面；无头调用方传入一个直接拒绝的交互实现。当记录在尝试期间被提交并被观察到时，`begin()` 报告 `{ status: 'authorized' }`；当人拒绝或调用方撤销时，报告 `{ status: 'cancelled' }`。`cancel(key)` 从第二次调用撤销正在运行的尝试，服务于那种用第二次调用来响应「取消」按钮、却不持有第一次调用 signal 的请求/响应式传输。\n\n### 可能出错的地方\n\n- **没有 flow 的凭据是惰性的**——对没有任何 flow 认领的键调用 `begin()` 会抛出 `NO_FLOW`；被卸载插件遗留的记录可以删除，但无法重新授权。\n- **每个凭据同时只允许一次尝试**——已有尝试在运行时再次 `begin()` 会抛出 `ALREADY_IN_FLIGHT`；entry 上的 `inFlight` 让界面预先禁用按钮。\n- **未提交就返回的 flow 会被拒绝**——抛出 `NOT_COMMITTED`，因此 `authorized` 永远意味着记录真的已存储。\n- **点名 flow 未提供的方法会抛出 `UNKNOWN_METHOD`**——不点名则运行 flow 的第一个方法。\n- **「不」是一种结果，不是故障**——被拒绝的 prompt 让尝试以 `cancelled` 结算，与撤销的 signal 完全一致；其余任何失败都以抛出的错误抵达调用方。\n\n-----\n\n<a id=\"understand-the-implementation\"></a>\n## 理解实现\n\n<details>\n<summary>实现细节——点击展开</summary>\n\n本节解释本 seam 背后的设计决策，并指出实现它们的代码位置；可观察行为已在[使用本包](#use-this-package)中完整说明。\n\n### 设计理念\n\n- **seam 拥有对话，从不拥有协议。** 知道如何取得自己那份凭据的插件，以它写入的记录为键注册一个 flow；第二种授权协议以另一个 flow 的形式到来，而不是另一个 seam，能渲染一个 flow 的界面就能渲染全部 flow。\n- **写入由 flow 拥有。** `run()` 返回即表示记录已通过 `ctx.credentials` 提交；seam 核实的是它在尝试期间观察到的提交——只看记录存在与否，会让重新授权把陈旧记录冒充成新鲜的——并拒绝未提交就返回的 flow。让提交发生在 flow 内部，才能使一个通过自有 store 适配器持久化的库保持为唯一写入方，而不是把凭据复制出来再写第二遍。\n- **交互随请求传入，而非注册表。** 发起授权的一方才是能与人对话的一方，因此提问恰好抵达发问的那个界面，无头调用方则传入一个直接拒绝的交互实现。这样既不存在「环境提供方缺席」的问题，也不会有某个提问该归两个已打开页面中哪一个的疑问。\n- **人的「不」是一种结果，不是故障。** 选择拒绝的交互实现以 `AuthorizationDeclinedError` 拒绝其 prompt，尝试以 `cancelled` 结算，与撤销的 signal 完全一致；其余任何 prompt 拒绝仍是抵达调用方的 flow 故障。\n\n### 源码地图\n\n| 文件 | 职责 |\n|---|---|\n| [`src/index.ts`](src/index.ts) | Service Definition：flow 注册表、每键单尝试生命周期、交互路由、提交确认 |\n| [`src/types.ts`](src/types.ts) | 跨进程安全的词汇：方法、notice、prompt、结果、entry |\n| [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件：`authorization/settled` 点名的键必已释放 |\n\n### 生命周期\n\n每个键同时只允许一次尝试。`begin()` 校验键与方法、拒绝繁忙键的第二次尝试，并用一个 `AuthorizationSession` 运行 flow——它携带所选方法、取消 signal 以及路由到请求交互的 `notify`/`prompt` 回调。被撤销的尝试会立即结算，即使 flow 从未响应它的 signal——被遗弃的运行任其自行结束，而它若仍设法提交了一条记录，那也是一条人确实授权过的记录。键在 `authorization/settled` 触发之前释放，因此以启动下一次尝试来响应的监听器不会被拒绝；监听器失败按凭据 seam 的规则就地遏制。\n\n### 交互词汇\n\nnotice 是单向的，且从不携带机密：一条消息，以及可选的「人需要打开的页面」与「需要在该页面输入的码」。prompt 是 flow 无法自行回答的问题——`text`、`secret` 或 `select`——其中 `secret` 与 `text` 的差别仅在呈现方式。prompt 自带 signal，使得让手输码与浏览器回调赛跑的 flow 可以在尝试继续的同时撤下落败的那个问题；撤销整次尝试则用请求的 signal。这套词汇刻意小于任何单个 provider 的词汇：它描述的是界面必须渲染什么，因此能渲染一个 flow 的界面就能渲染全部 flow。\n\n### 提交确认\n\n尝试期间，seam 监听该 flow 键上的 `credentials/record-updated`，`run()` 返回后再重读 `describeRecord`——确认提交确实发生在当下，因为在重新授权时记录早已存在，只看存在与否会让陈旧凭据冒充新鲜授权。未提交就返回的 flow，或删除记录而非提交的 flow，会抛出 `NOT_COMMITTED`。\n\n</details>\n\n-----\n\n<a id=\"further-exploration\"></a>\n## 进一步探索\n\n当包级约定不够用时阅读以下页面。它们从共享凭据词汇逐步进入 flow 写入的记录存储，以及本 seam 背后的决策证据。\n\n- [凭据子系统参考](../../../docs/subsystems/credentials.zh.md)——两个键空间与两个 seam 的生成 cordis 接口面。\n- [凭据包映射](../README.zh.md)——凭据引用、本地存储与授权三个包。\n- [凭据引用 seam](../credentials/README.zh.md)——每个 flow 都经由它提交的记录存储。\n- [能力 seam](../../../docs/capability-seams.zh.md)——本 seam 遵循的 Service Definition / Service Provider / Consumer 拆分。\n- [凭据记录与授权 flow](../../../.agents/notes/implemented/architecture/2026-08-13-credential-records-and-authorization-flows.zh.md)——记录半侧与本 seam 背后的理由与决策。\n\n-----\n\n<a id=\"model-experience\"></a>\n## 模型体验\n\n无，因为授权是配置期与人的对话，flow、notice 与 prompt 都不会抵达模型请求。\n\n#### KV Cache 影响\n\n不失效；任何授权状态都不会进入请求前缀。\n\n## 已知限制与延期工作\n\n<a id=\"known-limitations-and-deferred-work\"></a>\n\n\n这些限制说明本包何时不合适或需要特别注意。它们是当前包约束，不是任务积压。\n\n- **flow 不可恢复**——一次尝试只存活于发起它的进程中，因此登录途中刷新浏览器会丢弃它，人需要重来；可持久的尝试需要一个本 seam 并不具备的存储。\n- **没有吊销**——登出即 `ctx.credentials.deleteRecord(key)`，它只遗忘本地记录而不通知签发方；需要服务端吊销的 provider 没有可声明之处。\n- **没有 flow 的键是惰性的**——seam 只报告已注册的内容，因此被卸载插件遗留的记录可以删除但无法重新授权；识别这种孤儿记录由调用方负责，与 `listRecords()` 的情况相同。\n\n<a id=\"dev-note\"></a>\n### 开发备注\n\n<details>\n<summary>维护者的工作上下文——点击展开</summary>\n\n本开发备注是维护者的工作上下文：开放问题与尚未决定的探索方向。它明确不具权威性——已交付的行为、限制与既定理由以上文、包代码和相关 Agent Note 为准。\n\n上文限制点名的开放方向——可恢复的尝试、服务端吊销、孤儿记录发现——每一项落地前都需要各自的设计与存储。不变式伴生插件是唯一承重的运行时检查：结算时键必须已释放，因为卡死的键与繁忙的键无法区分，只有重启才能释放它。\n\n</details>\n","readmeFilename":"README.zh.md","_rev":"1-1aa2b7afe1e6dc6ef5d66d1f955ebf4b"}