@try-works/dsh-model-as-tool 0.1.3 → 0.4.0
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/bin/dsh-tool-mode-credentials.mjs +20 -0
- package/bin/dsh-tool-mode.mjs +202 -0
- package/catalog/desert-ant-models.json +693 -693
- package/client/client.model-tools.js +614 -0
- package/client/index.js +1717 -0
- package/lib/adapters/index.d.ts.map +1 -1
- package/lib/adapters/index.js +4 -4
- package/lib/bindings.d.ts.map +1 -1
- package/lib/bindings.js +26 -25
- package/lib/capability.d.ts.map +1 -1
- package/lib/capability.js +17 -0
- package/lib/capture-path.d.ts +97 -0
- package/lib/capture-path.d.ts.map +1 -0
- package/lib/capture-path.js +152 -0
- package/lib/diagnostics.d.ts +166 -0
- package/lib/diagnostics.d.ts.map +1 -0
- package/lib/diagnostics.js +135 -0
- package/lib/extension-dependencies.d.ts +325 -0
- package/lib/extension-dependencies.d.ts.map +1 -0
- package/lib/extension-dependencies.js +858 -0
- package/lib/extension-dispatch.d.ts +117 -0
- package/lib/extension-dispatch.d.ts.map +1 -0
- package/lib/extension-dispatch.js +124 -0
- package/lib/extension-registrar.d.ts +100 -0
- package/lib/extension-registrar.d.ts.map +1 -0
- package/lib/extension-registrar.js +134 -0
- package/lib/extensions-api.d.ts +221 -0
- package/lib/extensions-api.d.ts.map +1 -0
- package/lib/extensions-api.js +17 -0
- package/lib/extensions-remote.d.ts +54 -0
- package/lib/extensions-remote.d.ts.map +1 -0
- package/lib/extensions-remote.js +66 -0
- package/lib/extensions-service.d.ts +367 -0
- package/lib/extensions-service.d.ts.map +1 -0
- package/lib/extensions-service.js +1527 -0
- package/lib/harness-llm.d.ts +138 -0
- package/lib/harness-llm.d.ts.map +1 -0
- package/lib/harness-llm.js +218 -0
- package/lib/harness-serving.d.ts +95 -0
- package/lib/harness-serving.d.ts.map +1 -0
- package/lib/harness-serving.js +157 -0
- package/lib/headless.d.ts +97 -0
- package/lib/headless.d.ts.map +1 -0
- package/lib/headless.js +349 -0
- package/lib/http-acquisition.d.ts +27 -0
- package/lib/http-acquisition.d.ts.map +1 -0
- package/lib/http-acquisition.js +136 -0
- package/lib/index.d.ts +271 -7
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2644 -70
- package/lib/invocation-accounting.d.ts +169 -0
- package/lib/invocation-accounting.d.ts.map +1 -0
- package/lib/invocation-accounting.js +148 -0
- package/lib/legacy-migration.d.ts +95 -0
- package/lib/legacy-migration.d.ts.map +1 -0
- package/lib/legacy-migration.js +429 -0
- package/lib/managed-workers.d.ts +151 -0
- package/lib/managed-workers.d.ts.map +1 -0
- package/lib/managed-workers.js +249 -0
- package/lib/mcp/client-grant.d.ts +74 -0
- package/lib/mcp/client-grant.d.ts.map +1 -0
- package/lib/mcp/client-grant.js +60 -0
- package/lib/mcp/credential-cli.d.ts +38 -0
- package/lib/mcp/credential-cli.d.ts.map +1 -0
- package/lib/mcp/credential-cli.js +148 -0
- package/lib/mcp/credential-store.d.ts +143 -0
- package/lib/mcp/credential-store.d.ts.map +1 -0
- package/lib/mcp/credential-store.js +454 -0
- package/lib/mcp/host-binding.d.ts +25 -0
- package/lib/mcp/host-binding.d.ts.map +1 -1
- package/lib/mcp/host-binding.js +107 -14
- package/lib/mcp/http.d.ts +68 -18
- package/lib/mcp/http.d.ts.map +1 -1
- package/lib/mcp/http.js +390 -62
- package/lib/mcp/oauth.d.ts +149 -0
- package/lib/mcp/oauth.d.ts.map +1 -0
- package/lib/mcp/oauth.js +396 -0
- package/lib/mcp/protocol.d.ts +46 -7
- package/lib/mcp/protocol.d.ts.map +1 -1
- package/lib/mcp/protocol.js +63 -16
- package/lib/mcp/rate-limit.d.ts +68 -0
- package/lib/mcp/rate-limit.d.ts.map +1 -0
- package/lib/mcp/rate-limit.js +114 -0
- package/lib/mcp/sdk-v2.d.ts +17 -0
- package/lib/mcp/sdk-v2.d.ts.map +1 -0
- package/lib/mcp/sdk-v2.js +181 -0
- package/lib/mcp/server.d.ts +11 -1
- package/lib/mcp/server.d.ts.map +1 -1
- package/lib/mcp/server.js +80 -13
- package/lib/mcp/stdio.d.ts.map +1 -1
- package/lib/mcp/stdio.js +9 -1
- package/lib/mcp/subscriptions.d.ts +43 -1
- package/lib/mcp/subscriptions.d.ts.map +1 -1
- package/lib/mcp/subscriptions.js +90 -9
- package/lib/model-qualification.d.ts +192 -0
- package/lib/model-qualification.d.ts.map +1 -0
- package/lib/model-qualification.js +178 -0
- package/lib/planning.d.ts +246 -0
- package/lib/planning.d.ts.map +1 -0
- package/lib/planning.js +457 -0
- package/lib/prepared-assets.d.ts +178 -0
- package/lib/prepared-assets.d.ts.map +1 -0
- package/lib/prepared-assets.js +286 -0
- package/lib/provider-bridge.d.ts +37 -0
- package/lib/provider-bridge.d.ts.map +1 -1
- package/lib/provider-bridge.js +189 -12
- package/lib/ptc-authority.d.ts +140 -0
- package/lib/ptc-authority.d.ts.map +1 -0
- package/lib/ptc-authority.js +216 -0
- package/lib/ptc-runtime.d.ts +156 -0
- package/lib/ptc-runtime.d.ts.map +1 -0
- package/lib/ptc-runtime.js +191 -0
- package/lib/publication.d.ts +2 -1
- package/lib/publication.d.ts.map +1 -1
- package/lib/publication.js +13 -5
- package/lib/receipt.d.ts.map +1 -1
- package/lib/receipt.js +8 -0
- package/lib/title-proposal.d.ts +194 -0
- package/lib/title-proposal.d.ts.map +1 -0
- package/lib/title-proposal.js +244 -0
- package/lib/tool-mode-run.d.ts +235 -0
- package/lib/tool-mode-run.d.ts.map +1 -0
- package/lib/tool-mode-run.js +283 -0
- package/lib/types.d.ts +3 -1
- package/lib/types.d.ts.map +1 -1
- package/lib/update-surface.d.ts +79 -0
- package/lib/update-surface.d.ts.map +1 -0
- package/lib/update-surface.js +224 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.d.ts +38 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.js +445 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.d.ts +14 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.js +63 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.d.ts +14 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.js +76 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/dsh.d.ts.map +1 -1
- package/node_modules/@try-works/tool-mode-contracts/lib/dsh.js +69 -55
- package/node_modules/@try-works/tool-mode-contracts/lib/index.d.ts +4 -1
- package/node_modules/@try-works/tool-mode-contracts/lib/index.d.ts.map +1 -1
- package/node_modules/@try-works/tool-mode-contracts/lib/index.js +26 -2
- package/node_modules/@try-works/tool-mode-contracts/lib/registry.d.ts +28 -2
- package/node_modules/@try-works/tool-mode-contracts/lib/registry.d.ts.map +1 -1
- package/node_modules/@try-works/tool-mode-contracts/lib/registry.js +93 -10
- package/node_modules/@try-works/tool-mode-contracts/package.json +0 -1
- package/node_modules/@try-works/tool-mode-extension-sdk/bin/dsh-tool-mode.mjs +20 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.d.ts +80 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.js +533 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.d.ts +83 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.js +125 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.d.ts +70 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.js +201 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.d.ts +57 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.js +83 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.d.ts +32 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.js +32 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.d.ts +86 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.js +191 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.d.ts +126 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.js +297 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.d.ts +216 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.js +800 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.d.ts +210 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.js +368 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.d.ts +63 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.js +149 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.d.ts +35 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.js +179 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.d.ts +179 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.js +235 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.d.ts +367 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.js +807 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/tar-gzip-source.d.ts +44 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/tar-gzip-source.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/tar-gzip-source.js +243 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.d.ts +185 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.js +504 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.d.ts +17 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.js +80 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.d.ts +52 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.js +102 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.d.ts +171 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.js +271 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.d.ts +242 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.js +58 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.d.ts +118 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.js +113 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.d.ts +2 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.js +351 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.d.ts +150 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.js +670 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/package.json +53 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/schema/extension-manifest-1.1.0.schema.json +253 -0
- package/node_modules/@try-works/tool-mode-recipes/bin/tool-mode-recipe.mjs +15 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/broker.d.ts +427 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/broker.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/broker.js +592 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/cli.d.ts +60 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/cli.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/cli.js +936 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.d.ts +237 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.js +200 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.d.ts +85 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.js +176 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/images.d.ts +105 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/images.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/images.js +129 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/index.d.ts +25 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/index.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/index.js +25 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.d.ts +325 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.js +1310 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/templates.d.ts +51 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/templates.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/templates.js +310 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/titles.d.ts +227 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/titles.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/titles.js +407 -0
- package/node_modules/@try-works/tool-mode-recipes/package.json +56 -0
- package/node_modules/@try-works/tool-mode-recipes/recipes/describe-image.json +38 -0
- package/node_modules/@try-works/tool-mode-recipes/recipes/text-title.json +45 -0
- package/node_modules/@try-works/tool-mode-recipes/schema/harness-model-recipe.schema.json +273 -0
- package/package.json +43 -15
- package/remote/spec.mjs +462 -0
- package/remote/typert.host.mjs +14 -0
- package/remote/typert.remote-client.mjs +16 -0
- package/lib/adapters/tongue.d.ts +0 -31
- package/lib/adapters/tongue.d.ts.map +0 -1
- package/lib/adapters/tongue.js +0 -93
- package/lib/adapters/voz.d.ts +0 -28
- package/lib/adapters/voz.d.ts.map +0 -1
- package/lib/adapters/voz.js +0 -102
- /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.d.ts +0 -0
- /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.d.ts.map +0 -0
- /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.js +0 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The host's managed-worker registry (EXT-20, the host half).
|
|
3
|
+
*
|
|
4
|
+
* The SDK ships the mechanism — {@link ManagedExtensionWorker}, a scrubbed-environment
|
|
5
|
+
* child process speaking the bounded invoke protocol — and this module is the host-side
|
|
6
|
+
* LIFETIME discipline: ONE worker process per extension per activation GENERATION,
|
|
7
|
+
* reused across invocations, disposed the moment the generation advances (a
|
|
8
|
+
* re-activation gets a fresh process, the old one never serves another call).
|
|
9
|
+
*
|
|
10
|
+
* What stays OUT of the worker, by construction:
|
|
11
|
+
*
|
|
12
|
+
* - the grant: the host authorizes the caller BEFORE dispatch ever builds a provider,
|
|
13
|
+
* so `providerFor` here runs only for calls that already passed authorization, and
|
|
14
|
+
* the worker receives exactly `{implementationId, input}` — no grant, no caller
|
|
15
|
+
* identity, no configuration secrets;
|
|
16
|
+
* - the schemas: input validation happened host-side before the call, and the returned
|
|
17
|
+
* value is validated against the declared output schema by the same path that
|
|
18
|
+
* validates the in-process result — the worker's payload is treated as untrusted
|
|
19
|
+
* data exactly like an in-process provider's;
|
|
20
|
+
* - receipts and accounting: untouched — they record the call in the same code path.
|
|
21
|
+
*
|
|
22
|
+
* IDENTITY: the worker is keyed by the extension's TRUE MANIFEST ID, never by a path
|
|
23
|
+
* or a package specifier (an install path must not leak into diagnostics or collide
|
|
24
|
+
* with a package name). The SDK child also derives its implementation->model mapping
|
|
25
|
+
* from the owning package.json + the manifest beside the entry, so this registry
|
|
26
|
+
* STAGES a host-owned worker directory — package.json + the manifest + a junction to
|
|
27
|
+
* the entry's directory — when the entry's own package root does not carry them
|
|
28
|
+
* (a staged store copy extracts no manifest). The entry's REAL bytes are the ones the
|
|
29
|
+
* worker executes: the junction resolves to the verified installed copy, never a host
|
|
30
|
+
* rewrite.
|
|
31
|
+
*
|
|
32
|
+
* Every worker failure surfaces as the SDK's ManagementError vocabulary
|
|
33
|
+
* (EXTENSION_FAULT), which the dispatch path converts into a tool error; the host
|
|
34
|
+
* process is never affected.
|
|
35
|
+
*
|
|
36
|
+
* @module @try-works/dsh-model-as-tool/managed-workers
|
|
37
|
+
*/
|
|
38
|
+
import { ManagedExtensionWorker } from '@try-works/tool-mode-extension-sdk';
|
|
39
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
40
|
+
import { basename, dirname, join } from 'node:path';
|
|
41
|
+
import { tmpdir } from 'node:os';
|
|
42
|
+
/**
|
|
43
|
+
* Create the host's managed-worker registry.
|
|
44
|
+
* @returns the registry.
|
|
45
|
+
*/
|
|
46
|
+
export function createManagedWorkerRegistry() {
|
|
47
|
+
const holders = new Map();
|
|
48
|
+
/**
|
|
49
|
+
* Resolve the entry the CHILD must run, staging a host-owned owning-package
|
|
50
|
+
* layout when the entry's own package root does not carry one (a staged store
|
|
51
|
+
* copy extracts no package.json/manifest, which the SDK child needs for its
|
|
52
|
+
* implementation->model mapping). The staged dir carries a package.json and the
|
|
53
|
+
* REAL manifest bytes plus a junction to the entry's directory, so the bytes the
|
|
54
|
+
* worker executes are exactly the verified installed copy's.
|
|
55
|
+
*/
|
|
56
|
+
const resolveChildEntry = (binding) => {
|
|
57
|
+
// Walk up from the entry looking for the OWNING package: the nearest package.json
|
|
58
|
+
// that actually declares a toolMode.manifest. A store copy extracts neither, so a
|
|
59
|
+
// stray unrelated package.json higher up must NOT be mistaken for the owner (that
|
|
60
|
+
// would hand the child a package whose manifest it would then misread).
|
|
61
|
+
let root = dirname(binding.entryPath);
|
|
62
|
+
let owned = false;
|
|
63
|
+
for (;;) {
|
|
64
|
+
const candidate = join(root, 'package.json');
|
|
65
|
+
if (existsSync(candidate)) {
|
|
66
|
+
try {
|
|
67
|
+
const pkg = JSON.parse(readFileSync(candidate, 'utf8'));
|
|
68
|
+
if (typeof pkg.toolMode?.manifest === 'string') {
|
|
69
|
+
owned = true;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Unreadable or malformed: not an owner.
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const parent = dirname(root);
|
|
78
|
+
if (parent === root)
|
|
79
|
+
break;
|
|
80
|
+
root = parent;
|
|
81
|
+
}
|
|
82
|
+
if (owned)
|
|
83
|
+
return { entryPath: binding.entryPath, stagedDir: undefined };
|
|
84
|
+
if (binding.manifestPath === undefined) {
|
|
85
|
+
throw new Error(`the worker for ${binding.extensionId} cannot derive the model mapping: the entry's package root carries no `
|
|
86
|
+
+ 'package.json and the host holds no manifest path for it (a staged store copy). Until the SDK lands the '
|
|
87
|
+
+ 'host-supplied mapping API, this binding is refused rather than run with a guessed mapping.');
|
|
88
|
+
}
|
|
89
|
+
const stagedDir = join(tmpdir(), `tmw-${binding.extensionId.replace(/[^A-Za-z0-9._-]/g, '_')}-${binding.generation}-${process.pid}`);
|
|
90
|
+
mkdirSync(join(stagedDir, 'lib'), { recursive: true });
|
|
91
|
+
writeFileSync(join(stagedDir, 'package.json'), JSON.stringify({
|
|
92
|
+
name: binding.extensionId,
|
|
93
|
+
version: '0.0.0-worker',
|
|
94
|
+
type: 'module',
|
|
95
|
+
toolMode: { manifest: './tool-mode.extension.json' },
|
|
96
|
+
}));
|
|
97
|
+
// The REAL manifest bytes, so the child's identity and mapping checks run on
|
|
98
|
+
// exactly what the host verified.
|
|
99
|
+
writeFileSync(join(stagedDir, 'tool-mode.extension.json'), readFileSync(binding.manifestPath));
|
|
100
|
+
// A junction (Windows, no elevation) or symlink to the entry's directory — the
|
|
101
|
+
// child's mapping walks UP from the staged entry and finds the staged owner.
|
|
102
|
+
symlinkSync(dirname(binding.entryPath), join(stagedDir, 'lib'), process.platform === 'win32' ? 'junction' : 'dir');
|
|
103
|
+
return { entryPath: join(stagedDir, 'lib', basename(binding.entryPath)), stagedDir };
|
|
104
|
+
};
|
|
105
|
+
const bind = async (binding) => {
|
|
106
|
+
const current = holders.get(binding.extensionId);
|
|
107
|
+
if (current !== undefined && current.generation === binding.generation) {
|
|
108
|
+
return current;
|
|
109
|
+
}
|
|
110
|
+
// EXT-20: a generation change disposes the OLD worker before any new process is
|
|
111
|
+
// created — the previous generation's process never answers this generation's call.
|
|
112
|
+
if (current !== undefined) {
|
|
113
|
+
await current.worker.dispose();
|
|
114
|
+
if (current.stagedDir !== undefined)
|
|
115
|
+
rmSync(current.stagedDir, { recursive: true, force: true });
|
|
116
|
+
holders.delete(binding.extensionId);
|
|
117
|
+
}
|
|
118
|
+
const { entryPath, stagedDir } = resolveChildEntry(binding);
|
|
119
|
+
const holder = {
|
|
120
|
+
generation: binding.generation,
|
|
121
|
+
stagedDir,
|
|
122
|
+
worker: ManagedExtensionWorker.create({
|
|
123
|
+
extensionId: binding.extensionId,
|
|
124
|
+
entryPath,
|
|
125
|
+
generation: binding.generation,
|
|
126
|
+
...binding.envPassthrough === undefined ? {} : { envPassthrough: binding.envPassthrough },
|
|
127
|
+
}),
|
|
128
|
+
};
|
|
129
|
+
holders.set(binding.extensionId, holder);
|
|
130
|
+
return holder;
|
|
131
|
+
};
|
|
132
|
+
return {
|
|
133
|
+
async providerFor(binding) {
|
|
134
|
+
const holder = await bind(binding);
|
|
135
|
+
return {
|
|
136
|
+
async load(request) {
|
|
137
|
+
// The HOST-VERIFIED prepared assets reach the child through the
|
|
138
|
+
// SDK's load API (host-prepared assets ONLY — nothing else is sent,
|
|
139
|
+
// and the child never auto-loads). An empty map reaches the child as
|
|
140
|
+
// an empty map: the extension's own load refuses a missing group by
|
|
141
|
+
// name rather than the host inventing a verification.
|
|
142
|
+
await holder.worker.load(request.modelId, {
|
|
143
|
+
assets: request.assets,
|
|
144
|
+
signal: request.signal,
|
|
145
|
+
});
|
|
146
|
+
},
|
|
147
|
+
async invoke(request) {
|
|
148
|
+
// The GRANT and the schemas were already enforced host-side; the worker
|
|
149
|
+
// receives the validated input and nothing else. No resolvePath broker is
|
|
150
|
+
// supplied on the dispatch path: an extension tool needing a host-authorized
|
|
151
|
+
// path is refused by the child's own denial rather than given an identity
|
|
152
|
+
// resolver (the protocol's omitted-callback rule).
|
|
153
|
+
return await holder.worker.invoke({
|
|
154
|
+
implementationId: request.implementationId,
|
|
155
|
+
input: request.input,
|
|
156
|
+
...request.context?.signal === undefined
|
|
157
|
+
? {}
|
|
158
|
+
: { signal: request.context.signal },
|
|
159
|
+
});
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
async adapterFor(binding) {
|
|
164
|
+
const holder = await bind(binding);
|
|
165
|
+
let disposed = false;
|
|
166
|
+
return {
|
|
167
|
+
async inspect(modelId, options) {
|
|
168
|
+
if (disposed)
|
|
169
|
+
throw new Error(`the managed worker for ${binding.extensionId} was disposed`);
|
|
170
|
+
return await holder.worker.inspect(modelId, {
|
|
171
|
+
...options?.signal === undefined ? {} : { signal: options.signal },
|
|
172
|
+
});
|
|
173
|
+
},
|
|
174
|
+
async load(modelId, options) {
|
|
175
|
+
if (disposed)
|
|
176
|
+
throw new Error(`the managed worker for ${binding.extensionId} was disposed`);
|
|
177
|
+
// Host-prepared assets ONLY — the map the host verified; nothing else is
|
|
178
|
+
// sent to the child, and the child never auto-loads.
|
|
179
|
+
await holder.worker.load(modelId, {
|
|
180
|
+
assets: options.assets,
|
|
181
|
+
...options.signal === undefined ? {} : { signal: options.signal },
|
|
182
|
+
});
|
|
183
|
+
},
|
|
184
|
+
async unload(modelId, options) {
|
|
185
|
+
if (disposed)
|
|
186
|
+
throw new Error(`the managed worker for ${binding.extensionId} was disposed`);
|
|
187
|
+
await holder.worker.unload(modelId, {
|
|
188
|
+
...options?.signal === undefined ? {} : { signal: options.signal },
|
|
189
|
+
});
|
|
190
|
+
},
|
|
191
|
+
async invoke(request) {
|
|
192
|
+
if (disposed) {
|
|
193
|
+
throw new Error(`the managed worker for ${binding.extensionId} was disposed`);
|
|
194
|
+
}
|
|
195
|
+
return await holder.worker.invoke({
|
|
196
|
+
implementationId: request.implementationId,
|
|
197
|
+
input: request.input,
|
|
198
|
+
...request.signal === undefined ? {} : { signal: request.signal },
|
|
199
|
+
...request.resolvePath === undefined ? {} : { resolvePath: request.resolvePath },
|
|
200
|
+
});
|
|
201
|
+
},
|
|
202
|
+
async dispose() {
|
|
203
|
+
if (disposed)
|
|
204
|
+
return;
|
|
205
|
+
disposed = true;
|
|
206
|
+
await holder.worker.dispose();
|
|
207
|
+
if (holders.get(binding.extensionId) === holder)
|
|
208
|
+
holders.delete(binding.extensionId);
|
|
209
|
+
if (holder.stagedDir !== undefined)
|
|
210
|
+
rmSync(holder.stagedDir, { recursive: true, force: true });
|
|
211
|
+
},
|
|
212
|
+
pid() {
|
|
213
|
+
return holder.worker.pid;
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
},
|
|
217
|
+
async dispose(extensionId) {
|
|
218
|
+
const holder = holders.get(extensionId);
|
|
219
|
+
holders.delete(extensionId);
|
|
220
|
+
if (holder !== undefined) {
|
|
221
|
+
await holder.worker.dispose();
|
|
222
|
+
if (holder.stagedDir !== undefined)
|
|
223
|
+
rmSync(holder.stagedDir, { recursive: true, force: true });
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
capabilities() {
|
|
227
|
+
// The verified SDK child protocol now serves inspect, load/unload and the
|
|
228
|
+
// invoke resolvePath broker.
|
|
229
|
+
return { inspect: true, load: true };
|
|
230
|
+
},
|
|
231
|
+
bindings() {
|
|
232
|
+
return [...holders.entries()].map(([extensionId, holder]) => ({
|
|
233
|
+
extensionId,
|
|
234
|
+
generation: holder.generation,
|
|
235
|
+
pid: holder.worker.pid,
|
|
236
|
+
}));
|
|
237
|
+
},
|
|
238
|
+
async disposeAll() {
|
|
239
|
+
const live = [...holders.values()];
|
|
240
|
+
holders.clear();
|
|
241
|
+
await Promise.all(live.map(holder => holder.worker.dispose()));
|
|
242
|
+
for (const holder of live) {
|
|
243
|
+
if (holder.stagedDir !== undefined)
|
|
244
|
+
rmSync(holder.stagedDir, { recursive: true, force: true });
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
//# sourceMappingURL=managed-workers.js.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-client external grants (SEC-04, SEC-01 tails, SEC-03).
|
|
3
|
+
*
|
|
4
|
+
* Before this module, every external MCP call dispatched through the HOST'S OWN
|
|
5
|
+
* interactive grant: the surface's `execute` ran `ctx.tools.execute` under the operator's
|
|
6
|
+
* grant, with `callerKind: 'interactive'` — so an external client was the operator as far
|
|
7
|
+
* as authorization and receipts were concerned, and the round-65 review recorded exactly
|
|
8
|
+
* that as a false claim to the contrary. SEC-04 requires an ISOLATED external execution
|
|
9
|
+
* context per client instead.
|
|
10
|
+
*
|
|
11
|
+
* This module is the grant half of that isolation. It derives a grant per AUTHENTICATED
|
|
12
|
+
* client at the point where the identity is already verified — the HTTP layer, after the
|
|
13
|
+
* static-token or OAuth check — and the derived identity travels with the call as CALLER
|
|
14
|
+
* IDENTITY, never as a second dispatch path:
|
|
15
|
+
*
|
|
16
|
+
* - the DEFAULT allowed set is the live published surface (the registered contracts plus
|
|
17
|
+
* `tool_mode_run` — never `model_tools`, which stays host-local);
|
|
18
|
+
* - a per-client override narrows that set, so two clients can hold different grants;
|
|
19
|
+
* - the grant carries the CLIENT'S OWN `grantId` (`external:<clientId>`), recording
|
|
20
|
+
* policy and limits, so a receipt records who called rather than pretending the
|
|
21
|
+
* operator did — and a requested tool list can never extend any of it (BASE-05).
|
|
22
|
+
*
|
|
23
|
+
* @module @try-works/dsh-model-as-tool/mcp/client-grant
|
|
24
|
+
*/
|
|
25
|
+
import { type GrantLimits } from '../authority.ts';
|
|
26
|
+
/** The per-client grant policy an operator configures. */
|
|
27
|
+
export interface ClientGrantConfig {
|
|
28
|
+
/**
|
|
29
|
+
* The default allowed-tools set every authenticated client receives.
|
|
30
|
+
*
|
|
31
|
+
* Absent means the LIVE published surface at request time — the surface is the
|
|
32
|
+
* operator's export decision, so deriving the default from it keeps one source of
|
|
33
|
+
* truth. A non-empty list narrows the default for every client.
|
|
34
|
+
*/
|
|
35
|
+
readonly allowedTools?: readonly string[];
|
|
36
|
+
/** Whether the client's calls may be recorded; defaults to `receipts` (OBS-02). */
|
|
37
|
+
readonly recordingPolicy?: 'none' | 'receipts';
|
|
38
|
+
/** Resource limits; defaults are {@link DEFAULT_LIMITS}. */
|
|
39
|
+
readonly limits?: Partial<GrantLimits>;
|
|
40
|
+
/** Per-client overrides, keyed by the authenticated client identity. */
|
|
41
|
+
readonly perClient?: Readonly<Record<string, {
|
|
42
|
+
/** The client's own allowed set; absent inherits the default above. */
|
|
43
|
+
readonly allowedTools?: readonly string[];
|
|
44
|
+
readonly recordingPolicy?: 'none' | 'receipts';
|
|
45
|
+
readonly limits?: Partial<GrantLimits>;
|
|
46
|
+
}>>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The authenticated caller an external request runs as (SEC-04).
|
|
50
|
+
*
|
|
51
|
+
* Pure identity plus the derived grant — nothing executable, nothing caller-supplied.
|
|
52
|
+
*/
|
|
53
|
+
export interface ExternalCaller {
|
|
54
|
+
/** The authenticated client identity (static-token `clientId` or OAuth subject). */
|
|
55
|
+
readonly clientId: string;
|
|
56
|
+
/** The client's OWN grant identity, recorded in receipts — never the operator's. */
|
|
57
|
+
readonly grantId: string;
|
|
58
|
+
/** The tool names this client may invoke. */
|
|
59
|
+
readonly allowedTools: readonly string[];
|
|
60
|
+
readonly recordingPolicy: 'none' | 'receipts';
|
|
61
|
+
readonly limits: GrantLimits;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Derive one client's grant at authentication time.
|
|
65
|
+
*
|
|
66
|
+
* The client identity is INPUT — it must already be verified by the transport; nothing
|
|
67
|
+
* here authenticates, so a caller cannot reach this function with a chosen identity.
|
|
68
|
+
* @param clientId - the authenticated client identity.
|
|
69
|
+
* @param config - the operator's per-client grant policy, or undefined for the default.
|
|
70
|
+
* @param surfaceTools - the tool names the host currently publishes.
|
|
71
|
+
* @returns the derived caller identity and grant.
|
|
72
|
+
*/
|
|
73
|
+
export declare function resolveClientGrant(clientId: string, config: ClientGrantConfig | undefined, surfaceTools: readonly string[]): ExternalCaller;
|
|
74
|
+
//# sourceMappingURL=client-grant.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-grant.d.ts","sourceRoot":"","sources":["../../src/mcp/client-grant.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAElE,0DAA0D;AAC1D,MAAM,WAAW,iBAAiB;IAChC;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACzC,mFAAmF;IACnF,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;IAC9C,4DAA4D;IAC5D,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;IACtC,wEAAwE;IACxE,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE;QAC3C,uEAAuE;QACvE,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;QACzC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;QAC9C,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;KACvC,CAAC,CAAC,CAAA;CACJ;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,oFAAoF;IACpF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,oFAAoF;IACpF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,6CAA6C;IAC7C,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;IACxC,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,UAAU,CAAA;IAC7C,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;CAC7B;AAOD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,iBAAiB,GAAG,SAAS,EACrC,YAAY,EAAE,SAAS,MAAM,EAAE,GAC9B,cAAc,CAmBhB"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-client external grants (SEC-04, SEC-01 tails, SEC-03).
|
|
3
|
+
*
|
|
4
|
+
* Before this module, every external MCP call dispatched through the HOST'S OWN
|
|
5
|
+
* interactive grant: the surface's `execute` ran `ctx.tools.execute` under the operator's
|
|
6
|
+
* grant, with `callerKind: 'interactive'` — so an external client was the operator as far
|
|
7
|
+
* as authorization and receipts were concerned, and the round-65 review recorded exactly
|
|
8
|
+
* that as a false claim to the contrary. SEC-04 requires an ISOLATED external execution
|
|
9
|
+
* context per client instead.
|
|
10
|
+
*
|
|
11
|
+
* This module is the grant half of that isolation. It derives a grant per AUTHENTICATED
|
|
12
|
+
* client at the point where the identity is already verified — the HTTP layer, after the
|
|
13
|
+
* static-token or OAuth check — and the derived identity travels with the call as CALLER
|
|
14
|
+
* IDENTITY, never as a second dispatch path:
|
|
15
|
+
*
|
|
16
|
+
* - the DEFAULT allowed set is the live published surface (the registered contracts plus
|
|
17
|
+
* `tool_mode_run` — never `model_tools`, which stays host-local);
|
|
18
|
+
* - a per-client override narrows that set, so two clients can hold different grants;
|
|
19
|
+
* - the grant carries the CLIENT'S OWN `grantId` (`external:<clientId>`), recording
|
|
20
|
+
* policy and limits, so a receipt records who called rather than pretending the
|
|
21
|
+
* operator did — and a requested tool list can never extend any of it (BASE-05).
|
|
22
|
+
*
|
|
23
|
+
* @module @try-works/dsh-model-as-tool/mcp/client-grant
|
|
24
|
+
*/
|
|
25
|
+
import { DEFAULT_LIMITS } from "../authority.js";
|
|
26
|
+
/** Read a valid recording policy, defaulting to the observable one. */
|
|
27
|
+
function recordingPolicyOf(value) {
|
|
28
|
+
return value === 'none' ? 'none' : 'receipts';
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Derive one client's grant at authentication time.
|
|
32
|
+
*
|
|
33
|
+
* The client identity is INPUT — it must already be verified by the transport; nothing
|
|
34
|
+
* here authenticates, so a caller cannot reach this function with a chosen identity.
|
|
35
|
+
* @param clientId - the authenticated client identity.
|
|
36
|
+
* @param config - the operator's per-client grant policy, or undefined for the default.
|
|
37
|
+
* @param surfaceTools - the tool names the host currently publishes.
|
|
38
|
+
* @returns the derived caller identity and grant.
|
|
39
|
+
*/
|
|
40
|
+
export function resolveClientGrant(clientId, config, surfaceTools) {
|
|
41
|
+
const perClient = config?.perClient?.[clientId];
|
|
42
|
+
const requested = perClient?.allowedTools ?? config?.allowedTools ?? surfaceTools;
|
|
43
|
+
const onSurface = new Set(surfaceTools);
|
|
44
|
+
// H3: the grant is INTERSECTED with the live published surface. An override is a
|
|
45
|
+
// static list, and a typo in it must not silently grant a tool the operator never
|
|
46
|
+
// meant to name — while an override that names a tool the surface does not (yet)
|
|
47
|
+
// publish grants nothing for it rather than a phantom allowance. The DEFAULT
|
|
48
|
+
// (the surface itself) passes through the intersection unchanged.
|
|
49
|
+
const allowedTools = requested.filter(name => onSurface.has(name));
|
|
50
|
+
return {
|
|
51
|
+
clientId,
|
|
52
|
+
// The client's OWN identity: a receipt that says `external:<id>` names the caller
|
|
53
|
+
// without pretending the operator made the call.
|
|
54
|
+
grantId: `external:${clientId}`,
|
|
55
|
+
allowedTools,
|
|
56
|
+
recordingPolicy: recordingPolicyOf(perClient?.recordingPolicy ?? config?.recordingPolicy),
|
|
57
|
+
limits: { ...DEFAULT_LIMITS, ...config?.limits, ...perClient?.limits },
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=client-grant.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The HUMAN-ONLY credential CLI (SEC-01).
|
|
3
|
+
*
|
|
4
|
+
* The model-visible `model_tools credentials` surface REFUSES generation: a
|
|
5
|
+
* generated secret must never enter a model transcript. This module is the
|
|
6
|
+
* operator's terminal instead — it generates over the durable store and prints
|
|
7
|
+
* the secret EXACTLY ONCE to stdout, with the same stream discipline as the
|
|
8
|
+
* stdio bridge: stdout carries the credential and nothing else; every
|
|
9
|
+
* diagnostic goes to stderr. The secret is never logged, never persisted
|
|
10
|
+
* anywhere beyond the owner-only store document, and cannot be recovered —
|
|
11
|
+
* a lost secret is replaced by revoking and regenerating.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* dsh-tool-mode-credentials generate --scheme static-token --client-id <id>
|
|
15
|
+
* dsh-tool-mode-credentials generate --scheme oauth-resource-server --client-id <id> [--scope a,b,c]
|
|
16
|
+
* dsh-tool-mode-credentials list [--store <path>]
|
|
17
|
+
* dsh-tool-mode-credentials revoke --client-id <id> [--store <path>]
|
|
18
|
+
*
|
|
19
|
+
* Exit codes: 0 done; 1 the operation failed (corrupt store, duplicate id,
|
|
20
|
+
* persistence failure); 2 usage.
|
|
21
|
+
*
|
|
22
|
+
* @module @try-works/dsh-model-as-tool/mcp/credential-cli
|
|
23
|
+
*/
|
|
24
|
+
/** The two streams the CLI owns, so a test can capture them separately. */
|
|
25
|
+
export interface CliStreams {
|
|
26
|
+
/** The credential output — stdout in production, never anything else. */
|
|
27
|
+
out(line: string): void;
|
|
28
|
+
/** Diagnostics and usage — stderr in production. */
|
|
29
|
+
err(line: string): void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Run the credential CLI.
|
|
33
|
+
* @param argv - the process arguments, without the executable.
|
|
34
|
+
* @param streams - the two streams (stdout/stderr in production).
|
|
35
|
+
* @returns the process exit code.
|
|
36
|
+
*/
|
|
37
|
+
export declare function runCredentialsCli(argv: readonly string[], streams: CliStreams): number;
|
|
38
|
+
//# sourceMappingURL=credential-cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-cli.d.ts","sourceRoot":"","sources":["../../src/mcp/credential-cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AASH,2EAA2E;AAC3E,MAAM,WAAW,UAAU;IACzB,yEAAyE;IACzE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,oDAAoD;IACpD,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AA0BD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,UAAU,GAAG,MAAM,CA4FtF"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The HUMAN-ONLY credential CLI (SEC-01).
|
|
3
|
+
*
|
|
4
|
+
* The model-visible `model_tools credentials` surface REFUSES generation: a
|
|
5
|
+
* generated secret must never enter a model transcript. This module is the
|
|
6
|
+
* operator's terminal instead — it generates over the durable store and prints
|
|
7
|
+
* the secret EXACTLY ONCE to stdout, with the same stream discipline as the
|
|
8
|
+
* stdio bridge: stdout carries the credential and nothing else; every
|
|
9
|
+
* diagnostic goes to stderr. The secret is never logged, never persisted
|
|
10
|
+
* anywhere beyond the owner-only store document, and cannot be recovered —
|
|
11
|
+
* a lost secret is replaced by revoking and regenerating.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* dsh-tool-mode-credentials generate --scheme static-token --client-id <id>
|
|
15
|
+
* dsh-tool-mode-credentials generate --scheme oauth-resource-server --client-id <id> [--scope a,b,c]
|
|
16
|
+
* dsh-tool-mode-credentials list [--store <path>]
|
|
17
|
+
* dsh-tool-mode-credentials revoke --client-id <id> [--store <path>]
|
|
18
|
+
*
|
|
19
|
+
* Exit codes: 0 done; 1 the operation failed (corrupt store, duplicate id,
|
|
20
|
+
* persistence failure); 2 usage.
|
|
21
|
+
*
|
|
22
|
+
* @module @try-works/dsh-model-as-tool/mcp/credential-cli
|
|
23
|
+
*/
|
|
24
|
+
import { createFileCredentialStore, defaultCredentialStoreFile, generateCredential, } from "./credential-store.js";
|
|
25
|
+
const USAGE = [
|
|
26
|
+
'usage:',
|
|
27
|
+
' dsh-tool-mode-credentials generate --scheme static-token --client-id <id>',
|
|
28
|
+
' dsh-tool-mode-credentials generate --scheme oauth-resource-server --client-id <id> [--scope a,b,c]',
|
|
29
|
+
' dsh-tool-mode-credentials list [--store <path>]',
|
|
30
|
+
' dsh-tool-mode-credentials revoke --client-id <id> [--store <path>]',
|
|
31
|
+
'The generated secret is printed ONCE to stdout and is never echoed again;',
|
|
32
|
+
'a lost secret is replaced by revoking the client and generating a new one.',
|
|
33
|
+
].join('\n');
|
|
34
|
+
/** Parse `--flag value` pairs; repeated flags keep the last value. */
|
|
35
|
+
function parseArgs(argv) {
|
|
36
|
+
const pairs = new Map();
|
|
37
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
38
|
+
const token = argv[i];
|
|
39
|
+
if (!token.startsWith('--'))
|
|
40
|
+
throw new Error(`unexpected argument ${JSON.stringify(token)}`);
|
|
41
|
+
const value = argv[i + 1];
|
|
42
|
+
if (value === undefined || value.startsWith('--'))
|
|
43
|
+
throw new Error(`the flag ${token} needs a value`);
|
|
44
|
+
pairs.set(token.slice(2), value);
|
|
45
|
+
i += 1;
|
|
46
|
+
}
|
|
47
|
+
return pairs;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Run the credential CLI.
|
|
51
|
+
* @param argv - the process arguments, without the executable.
|
|
52
|
+
* @param streams - the two streams (stdout/stderr in production).
|
|
53
|
+
* @returns the process exit code.
|
|
54
|
+
*/
|
|
55
|
+
export function runCredentialsCli(argv, streams) {
|
|
56
|
+
const command = argv[0];
|
|
57
|
+
let pairs;
|
|
58
|
+
try {
|
|
59
|
+
pairs = parseArgs(argv.slice(1));
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
streams.err(error instanceof Error ? error.message : String(error));
|
|
63
|
+
streams.err(USAGE);
|
|
64
|
+
return 2;
|
|
65
|
+
}
|
|
66
|
+
const storeFile = pairs.get('store') ?? defaultCredentialStoreFile();
|
|
67
|
+
let store;
|
|
68
|
+
try {
|
|
69
|
+
store = createFileCredentialStore(storeFile);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
streams.err(`cannot open the credential store: ${error instanceof Error ? error.message : String(error)}`);
|
|
73
|
+
return 1;
|
|
74
|
+
}
|
|
75
|
+
if (command === 'generate') {
|
|
76
|
+
const scheme = pairs.get('scheme');
|
|
77
|
+
const clientId = pairs.get('client-id');
|
|
78
|
+
if (scheme !== 'static-token' && scheme !== 'oauth-resource-server') {
|
|
79
|
+
streams.err('generate requires --scheme static-token or --scheme oauth-resource-server');
|
|
80
|
+
streams.err(USAGE);
|
|
81
|
+
return 2;
|
|
82
|
+
}
|
|
83
|
+
if (clientId === undefined || clientId.length === 0) {
|
|
84
|
+
streams.err('generate requires --client-id');
|
|
85
|
+
streams.err(USAGE);
|
|
86
|
+
return 2;
|
|
87
|
+
}
|
|
88
|
+
const scope = pairs.get('scope') === undefined
|
|
89
|
+
? undefined
|
|
90
|
+
: pairs.get('scope').split(',').map(name => name.trim()).filter(name => name.length > 0);
|
|
91
|
+
try {
|
|
92
|
+
const generated = generateCredential(store, {
|
|
93
|
+
clientId,
|
|
94
|
+
scheme: scheme,
|
|
95
|
+
...scope === undefined ? {} : { scope },
|
|
96
|
+
});
|
|
97
|
+
// THE ONCE-ONLY OUTPUT. Nothing else in this process writes to stdout.
|
|
98
|
+
streams.out('Generated credential (SEC-01).');
|
|
99
|
+
streams.out(`clientId: ${generated.clientId}`);
|
|
100
|
+
streams.out(`scheme: ${generated.scheme}`);
|
|
101
|
+
streams.out(`scope: ${generated.scope.join(' ')}`);
|
|
102
|
+
streams.out(`secret: ${generated.secret}`);
|
|
103
|
+
streams.out('SHOWN ONCE: store the secret now. It is never echoed again and cannot be recovered;');
|
|
104
|
+
streams.out('a lost secret is replaced by revoking this client and generating a new credential.');
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
streams.err(`generation refused: ${error instanceof Error ? error.message : String(error)}`);
|
|
109
|
+
return 1;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (command === 'list') {
|
|
113
|
+
try {
|
|
114
|
+
const entries = store.list();
|
|
115
|
+
streams.out(`${entries.length} live client credential(s):`);
|
|
116
|
+
for (const entry of entries) {
|
|
117
|
+
streams.out(`- ${entry.clientId} (${entry.scheme}, ${entry.createdAt})`);
|
|
118
|
+
}
|
|
119
|
+
return 0;
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
streams.err(`list failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
123
|
+
return 1;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (command === 'revoke') {
|
|
127
|
+
const clientId = pairs.get('client-id');
|
|
128
|
+
if (clientId === undefined || clientId.length === 0) {
|
|
129
|
+
streams.err('revoke requires --client-id');
|
|
130
|
+
streams.err(USAGE);
|
|
131
|
+
return 2;
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const existed = store.get(clientId) !== undefined;
|
|
135
|
+
store.delete(clientId);
|
|
136
|
+
streams.out(existed ? `revoked ${clientId}` : `no credential for ${clientId}; nothing to revoke`);
|
|
137
|
+
return 0;
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
streams.err(`revoke refused: ${error instanceof Error ? error.message : String(error)}`);
|
|
141
|
+
return 1;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
streams.err(`unknown command ${JSON.stringify(command ?? '')}`);
|
|
145
|
+
streams.err(USAGE);
|
|
146
|
+
return 2;
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=credential-cli.js.map
|