@inixiative/signet

中文文档
0.7.0发布于 2026/11/01MITTypeScriptES Module

Present Kingdom Signets: the wire contracts, DPoP proofs, device pairing and a renewing client shared by Kingdom, Archive, Foundry and Oracle.

原始使用文档

@inixiative/signet

Present Kingdom Signets.

A Signet is Kingdom's only grant for using an integration. It names the resources, operations, lens and limits, and it can be revoked. Its grantee is a user or an integration, for example a paired Foundry or a machine's local Archive. The grantee presents it with an access token plus a DPoP proof signed by its device key (RFC 9449).

This package is the one implementation of that protocol, shared by Kingdom (which verifies) and Archive, Foundry and Oracle (which present):

  • contracts: the wire schemas: keys, lenses, proposals, pairing requests, delivered credentials, describe and execute, and the conversation frames.
  • proof: create and verify DPoP proofs (ES256, nonce-bound, access-token-bound).
  • files: private 0600 JSON files for keys and credentials.
  • client:
    • an Installation (a local Foundry, Archive or Oracle) is an actor known to Kingdom by its key: registerInstallation, then requestRegistration asks for what it needs (the owner it expects, by organization id or slug, and resources by id or by kind and name, with their operations) and returns a review code a person claims in Kingdom (a registerIntegration Inquiry), where the review opens with that owner and those resources selected, then collectSignet and saveCollectedSignet. installationInquiries, installationSignets, cancelInstallationInquiry, enrollInstallationSignet and rotateInstallationKey act as the installation;
    • pairInstallation does the whole pairing (register, request, wait on the socket, confirm the owner, collect) and holdInstallationSignets keeps one credential file per Signet the installation's integrations hold, enrolling later grants and dropping revoked ones;
    • InstallationSocket, Kingdom's live line to an installation: it proves the key, receives a snapshot of the installation's inquiries and Signets on connect and on every change, keeps the installation's integrations marked online, advertises its viewer URL, and polls the same reads while the socket is down;
    • a person or their agent acts as themselves with a Kingdom API token plus a proof from its key: requestSignet asks for a Signet on an integration (a signetRequest Inquiry, approved at once when the caller manages the integration), awaitSignet polls collectSignet until it is approved, and enrollSignet enrolls the key on a Signet an owner granted directly (owner/grant). saveCollectedSignet writes either result next to the key;
    • SignetClient, which renews its access token and signs each call: describe, execute, task settlement, and the run actions a Foundry uses for inference (resolveRun, delegateRun, renewRun, revokeRun), and for a subscription run releaseSubscription and reportSubscriptionLimits.
import { holdInstallationSignets, InstallationSocket, pairInstallation, SignetClient } from '@inixiative/signet';

const paired = await pairInstallation({
  url: kingdom, root: dataDir, kind: 'archive', name: 'Laptop Archive', sourceId,
  terms: {
    name: 'Laptop Archive', lifecycle: 'ongoing', expiresAt: null, maxRequests: null, maxConcurrent: 4,
    owner: { ownerModel: 'Organization', organization: 'inixiative' },
    resources: [{ resourceKind: 'archiveLibrary', operations: ['sessions.write'] }],
  },
  onReview: ({ review }) => console.log(`Approve in Kingdom: ${review}`),
  confirmOwner: async ({ ownerName }) => confirm(`Pair with ${ownerName}?`),
});

new InstallationSocket({
  url: kingdom,
  keyFile: paired.keyFile,
  onSnapshot: ({ signets }) => holdInstallationSignets(kingdom, paired.keyFile, paired.directory, signets),
}).start();

const signet = await SignetClient.fromFile(paired.credentialFile);
await signet.execute({ integrationId, operation: 'sessions.write', input: { resourceId, snapshot, previousDigest: null } });

A person's agent, with an API token minted in Kingdom:

import { awaitSignet, enrollSignet, generateSignetKey, requestSignet, saveCollectedSignet, SignetClient, writePrivateJson } from '@inixiative/signet';

await writePrivateJson(keyFile, generateSignetKey());
const { requestId, reviewCode } = await requestSignet(kingdom, keyFile, apiToken, {
  name: 'Research agent', integrationId, lifecycle: 'ongoing', expiresAt: null, maxRequests: null, maxConcurrent: 1,
  resources: [{ resourceId, operations: ['documents.read'] }],
});
const collected = await awaitSignet(kingdom, keyFile, requestId); // or, for a granted Signet:
// const collected = await enrollSignet(kingdom, keyFile, apiToken, { signetId, expectedRevision, name: 'Research agent' });
await saveCollectedSignet(credentialFile, kingdom, keyFile, collected);
const signet = await SignetClient.fromFile(credentialFile);
await signet.describe();

Declared operations

An integration whose operations Kingdom executes generically declares them here, one module per resource kind: threads.ts for a Foundry's threads resource, archive.ts for an Archive's archiveLibrary. Each declares its operation enum, display names, the input each operation takes, its lens schema and the lens's narrowing. declaredOperationSchema and operationInputSchemas join them (operation names are unique across contracts), operationInputKeys(operation) gives the access/execute keys an operation takes besides resourceId and limit, and operationInputFromAccess(operation, input) picks and parses exactly those. Kingdom's catalog, its input check and its dispatch read these, so a new operation is added here first.

  • Archive (archiveLibrary): documents.read, documents.search, sessions.write, sessions.delete. The lens is { documentIds?, fields? } ({} reaches every document) and narrowsArchiveLens narrows it. archiveSourceOperations (sessions.write, sessions.delete) are a paired local Archive acting on its own sessions: Kingdom binds them to its sourceId, so their grant takes no lens. archiveLensRefusal(operation, lens) says why a grant can't carry a lens (search needs content over every document).

Agent conversations

A conversation wraps a host Foundry's main thread (Kingdom docs/AGENT-CONVERSATIONS.md, Foundry docs/reference/CONVERSATIONS.md). A member is a Signet on the host Foundry's one threads resource; Kingdom admits each call and relays it over the host's Installation socket, and stores no conversation or message.

  • Lens. signetLensSchema is a union of strict shapes, archiveLensSchema | threadsLensSchema, because a resource grant names its resource by id with no kind. resourceLensSchema(kind) gives the shape a kind takes; Kingdom checks every grant with it against the kind it stores. A threads lens is { threads, view: 'own' | 'thread', delivery: 'note' | 'turn', outbound: 'review' | 'auto' }, the terms defaulting to the narrowest value. threads is a selector, a union on form: v1 builds { form: 'ids', ids } (1–100 thread ids); withMember and tag are named for standing Signets and fail to parse everywhere until a release adds them, so a new form never widens access silently. narrowsSignetLens is the rule for approving someone else's request: a subset of ids, own < thread, note < turn, review < auto.
  • Join requests may name a resource by kind: requestSignet takes { resourceKind: 'threads', operations, lens }, and Kingdom resolves it before review.
  • Per-entry reads. installationInquiries, installationSignets, the socket snapshot and describe read each entry on its own. An entry this package can't read is dropped and listed in dropped, and the rest is kept. Inside a Signet, resources are read one by one: a resource with a lens this package can't read (a selector form from a newer Kingdom, a lens that doesn't fit its resource's kind) is dropped, and the Signet stays, so an installation never loses a held Signet, or mistakes a member for revoked, over one resource. The snapshot carries hostedSignets (members of this Foundry's conversations) and hostedSignetRequests (join requests aimed at its integrations); held Signets carry their resources.
  • Host. InstallationSocket takes threads, one handler per operation (thread.read, thread.prompt, thread.floor, prompt.resolve, thread.subscribe), answering threadResult(...) or threadRefused(code). A frame with an unknown selector is refused unknown_selector before any handler runs. thread.subscribe answers with the member's view and the journalSeq it was taken at, and the subscription starts there: appendThread(subscriptionId, entry) holds entries pushed meanwhile and never sends one twice. Each append carries a dense per-subscription seq (a filtered view skips journal entries, so journalSeq can't reveal loss); an entry that can't be sent still spends its seq, so Kingdom sees the gap and the member resyncs. A subscription ends on thread.unsubscribe, when Kingdom stops re-asserting its lease (thread.lease; dropped 90 s after the last one, so one late 45 s sweep is survived), or when the socket drops.
  • Member. An outside agent holds its live views with SignetSocket: it authenticates as its Signet's presentation (authenticateSignet, a proof bound to POST /api/v1/access/signetSocket with ath), renews it before the five-minute token expires (renewPresentation), and opens one accessThread stream per thread: a snapshot, then appends, with Kingdom's seq/resync rules. Frames are handled in order, one open is in flight per stream, and Kingdom's close carries the reason: after a revision the stream reopens under the new lens, while the host is offline it retries with backoff, when the presentation lapsed the socket re-presents and reopens, and on revocation or expiry it ends (onClosed). Posts are separate execute calls (thread.prompt), each admitted and metered. A Foundry holding a conversation Signet opens the same streams over its Installation socket (threadStreams, openThread).
import { SignetClient, SignetSocket } from '@inixiative/signet';

const signet = await SignetClient.fromFile(credentialFile);
const socket = new SignetSocket({
  client: signet,
  onSnapshot: ({ threadId }, view) => show(threadId, view.entries),
  onEntry: ({ threadId }, entry) => append(threadId, entry),
  onClosed: ({ threadId }, reason) => console.log(`left ${threadId}: ${reason}`),
});
socket.open(threadId);
socket.start();
await signet.execute({
  integrationId,
  operation: 'thread.prompt',
  input: { resourceId, threadId, postId: crypto.randomUUID(), text: 'The role you wanted opened' },
});

Kingdom's OpenAPI spec (/openapi/docs) publishes each step's raw HTTP call and credentials.

MIT

版本与发布记录

@inixiative/signet 共发布 16 个版本,首次发布于 2026/10/08;过去 12 个月发布了 16 个版本。

版本发布日期距今
0.7.0latest2026/11/012 天前
0.6.12026/11/012 天前
0.6.02026/11/012 天前
0.5.12026/10/2014 天前
0.5.02026/10/2014 天前
0.4.62026/10/1816 天前

关于 @inixiative/signet 的常见问题

@inixiative/signet 是什么?

当前王国印章:由王国、Archive、Foundry和Oracle共享的有线合同、DPoP证明、设备配对和更新客户端。

如何安装 @inixiative/signet?

在项目目录运行 npm install @inixiative/signet,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:zod(^4.3.6)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@inixiative/signet 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@inixiative/signet 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@inixiative/signet 可以商用吗?

@inixiative/signet 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@inixiative/signet 还在维护吗?

最新版本 0.7.0 发布于 2026/11/01(2 天前);至今共发布 16 个版本,其中过去 12 个月发布了 16 个;首次发布于 2026/10/08。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。