melchizedek-agents 1.2.0 → 1.3.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.
Files changed (65) hide show
  1. package/.env.example +9 -0
  2. package/CHANGELOG.md +141 -0
  3. package/DOCUMENTATION.md +107 -10
  4. package/ONBOARDING.md +50 -11
  5. package/QUICKSTART.md +1 -1
  6. package/README.md +22 -2
  7. package/config/agents/examples/connectors.yaml +82 -0
  8. package/config/agents/syndicate.schema.json +324 -26
  9. package/dist/lib/a2a/app.js +3 -0
  10. package/dist/lib/a2a/oauthSetup.js +20 -5
  11. package/dist/lib/chatgpt/adapter.d.ts +50 -0
  12. package/dist/lib/chatgpt/adapter.js +107 -0
  13. package/dist/lib/chatgpt/cli.d.ts +23 -0
  14. package/dist/lib/chatgpt/cli.js +77 -0
  15. package/dist/lib/chatgpt/oauth.d.ts +103 -0
  16. package/dist/lib/chatgpt/oauth.js +448 -0
  17. package/dist/lib/chatgpt/state.d.ts +98 -0
  18. package/dist/lib/chatgpt/state.js +145 -0
  19. package/dist/lib/chatgpt/store.d.ts +24 -0
  20. package/dist/lib/chatgpt/store.js +94 -0
  21. package/dist/lib/compile.js +40 -25
  22. package/dist/lib/dispatch.js +6 -2
  23. package/dist/lib/doctor.d.ts +34 -3
  24. package/dist/lib/doctor.js +63 -5
  25. package/dist/lib/loadSyndicate.d.ts +6 -0
  26. package/dist/lib/mcp/server.d.ts +195 -0
  27. package/dist/lib/mcp/server.js +564 -0
  28. package/dist/lib/memory/supabaseMemoryService.js +2 -2
  29. package/dist/lib/models/adapterResolver.js +6 -1
  30. package/dist/lib/models/gateway.d.ts +1 -1
  31. package/dist/lib/models/gateway.js +4 -2
  32. package/dist/lib/onboarding.d.ts +4 -3
  33. package/dist/lib/onboarding.js +48 -9
  34. package/dist/lib/runtime/syndicateTurn.js +30 -16
  35. package/dist/lib/syndicateSchema.d.ts +146 -8
  36. package/dist/lib/syndicateSchema.js +118 -11
  37. package/dist/lib/tools/credentialUses.d.ts +7 -1
  38. package/dist/lib/tools/credentialUses.js +10 -2
  39. package/dist/lib/tools/generateImageTool.js +2 -1
  40. package/dist/lib/tools/inspectImageTool.js +1 -1
  41. package/dist/lib/tools/mcpToolFactory.d.ts +29 -1
  42. package/dist/lib/tools/mcpToolFactory.js +71 -25
  43. package/dist/lib/tools/mcpTransports.d.ts +12 -0
  44. package/dist/lib/tools/mcpTransports.js +11 -0
  45. package/dist/lib/tools/oauthConsent.d.ts +18 -3
  46. package/dist/lib/tools/oauthConsent.js +49 -14
  47. package/dist/lib/tools/oauthDiscovery.d.ts +202 -0
  48. package/dist/lib/tools/oauthDiscovery.js +616 -0
  49. package/dist/lib/tools/oauthTools.d.ts +44 -12
  50. package/dist/lib/tools/oauthTools.js +79 -22
  51. package/dist/lib/tools/xApiSearchTool.js +2 -2
  52. package/dist/scripts/a2a_server.js +8 -0
  53. package/dist/scripts/assistant_worker.js +11 -1
  54. package/dist/scripts/doctor.d.ts +1 -1
  55. package/dist/scripts/doctor.js +1 -1
  56. package/dist/scripts/mcp_server.d.ts +12 -0
  57. package/dist/scripts/mcp_server.js +345 -0
  58. package/dist/scripts/setup.d.ts +2 -0
  59. package/dist/scripts/setup.js +27 -0
  60. package/dist/scripts/syndicate_chat.js +12 -3
  61. package/package.json +5 -2
  62. package/skills/README.md +2 -2
  63. package/skills/briefs/skills-readme.md +1 -1
  64. package/skills/melchizedek-onboard/SKILL.md +28 -6
  65. package/skills/melchizedek-onboard-keys/SKILL.md +17 -5
@@ -0,0 +1,107 @@
1
+ /**
2
+ * lib/chatgpt/adapter.ts — GPT on the person's ChatGPT plan, through Sign in
3
+ * with ChatGPT, on their own machine only (ADR 0126).
4
+ *
5
+ * It is GptAdapter (the Responses API) with a different credential and the
6
+ * request shape OpenAI documents for plan usage
7
+ * (developers.openai.com/siwc/token-sharing-open-source/models-and-inference
8
+ * and …/preview-limitations):
9
+ * - always `POST https://api.openai.com/v1/responses`, never a proxy or a
10
+ * platform, with the stored access token as the bearer, refreshed as it
11
+ * nears expiry (lib/chatgpt/oauth.ts freshAccessToken), and no
12
+ * organization or project header;
13
+ * - `stream: true` and `store: false` on every request; a call that did
14
+ * not ask to stream is streamed and folded back into one final;
15
+ * - none of the parameters the preview refuses: `max_output_tokens`,
16
+ * `temperature`, `top_p`;
17
+ * - no SDK retries: a plan-usage limit (429) is reported, not hammered.
18
+ * The provider id stays `openai`, so reasoning replay and the stored events
19
+ * are the same as on a key.
20
+ *
21
+ * On a served surface (lib/chatgpt/state.ts markServedSurface) every call
22
+ * ends in a CHATGPT_SIGNIN_LOCAL_ONLY final before any token is read.
23
+ */
24
+ import { GptAdapter } from '../models/gptAdapter.js';
25
+ import { OPENAI_API_RESOURCE, SignInError, freshAccessToken } from './oauth.js';
26
+ import { servedSurface, servedSurfaceMessage, signInFile } from './state.js';
27
+ function failure(code, message) {
28
+ return { partial: false, parts: [], finishReason: 'error', error: { code, message, retryable: false } };
29
+ }
30
+ export class ChatGptSignInAdapter extends GptAdapter {
31
+ label = 'OpenAI (Sign in with ChatGPT)';
32
+ #file;
33
+ #oauth;
34
+ constructor(options) {
35
+ super({ model: options.model, endpoint: { platform: 'direct' } });
36
+ this.#file = options.file ?? signInFile();
37
+ this.#oauth = options.issuer ? { issuer: options.issuer } : {};
38
+ }
39
+ endpoint() {
40
+ return { platform: 'direct' };
41
+ }
42
+ baseURL() {
43
+ return OPENAI_API_RESOURCE;
44
+ }
45
+ missingKeyMessage() {
46
+ return 'No ChatGPT sign-in is stored; run `melchizedek-setup --chatgpt-signin`.';
47
+ }
48
+ /** The token source: read (and refreshed) on every request, never held in the adapter. */
49
+ clientAuth() {
50
+ return { apiKey: () => freshAccessToken(this.#file, this.#oauth), baseURL: OPENAI_API_RESOURCE };
51
+ }
52
+ clientOptions() {
53
+ return {
54
+ organization: null,
55
+ project: null,
56
+ maxRetries: 0,
57
+ logLevel: 'warn',
58
+ fetchOptions: { redirect: 'error' },
59
+ };
60
+ }
61
+ /** The preview takes none of the sampling fields. */
62
+ acceptsSampling() {
63
+ return false;
64
+ }
65
+ requestBody(request, model, endpoint) {
66
+ const body = super.requestBody(request, model, endpoint);
67
+ delete body.max_output_tokens;
68
+ delete body.temperature;
69
+ delete body.top_p;
70
+ body.store = false;
71
+ return body;
72
+ }
73
+ async *generate(request) {
74
+ const surface = servedSurface();
75
+ if (surface) {
76
+ yield failure('CHATGPT_SIGNIN_LOCAL_ONLY', servedSurfaceMessage(surface));
77
+ return;
78
+ }
79
+ // Check the sign-in first, so a missing or expired one is a plain final, not an SDK error.
80
+ try {
81
+ await freshAccessToken(this.#file, this.#oauth);
82
+ }
83
+ catch (err) {
84
+ const code = err instanceof SignInError ? err.code : 'signin_unreadable';
85
+ yield failure(code === 'not_signed_in' ? 'MISSING_API_KEY' : 'CHATGPT_SIGNIN_ERROR', err instanceof Error ? err.message : String(err));
86
+ return;
87
+ }
88
+ if (request.stream === true) {
89
+ yield* super.generate(request);
90
+ return;
91
+ }
92
+ // The plan-usage route streams only: stream, then give the caller what a
93
+ // non-streamed call gives — at most one thinking partial, then the final.
94
+ const thoughts = [];
95
+ for await (const r of super.generate({ ...request, stream: true })) {
96
+ if (r.partial) {
97
+ for (const p of r.parts)
98
+ if (p.type === 'thinking' && p.text)
99
+ thoughts.push(p.text);
100
+ continue;
101
+ }
102
+ if (thoughts.length > 0 && !r.error)
103
+ yield { partial: true, parts: [{ type: 'thinking', text: thoughts.join('') }] };
104
+ yield r;
105
+ }
106
+ }
107
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * lib/chatgpt/cli.ts — the terminal side of Sign in with ChatGPT (ADR 0126),
3
+ * shared by `melchizedek-setup --chatgpt-signin | --chatgpt-signout |
4
+ * --chatgpt-status` and `melchizedek-chat --chatgpt-signin`.
5
+ *
6
+ * Prints the authorization link (it carries no secret: a one-time state and
7
+ * a PKCE challenge), the file the credential lives in, and what the sign-in
8
+ * now carries. Never a token.
9
+ */
10
+ import type { OAuthOptions } from './oauth.ts';
11
+ export interface ChatGptCliOptions extends OAuthOptions {
12
+ log?: (line: string) => void;
13
+ env?: NodeJS.ProcessEnv;
14
+ port?: number;
15
+ openBrowser?: (url: string) => Promise<void>;
16
+ timeoutMs?: number;
17
+ }
18
+ /** The browser sign-in, from a terminal. Returns an exit code. */
19
+ export declare function runChatGptSignIn(opts?: ChatGptCliOptions): Promise<number>;
20
+ /** Sign out: tokens removed locally, refresh token revoked at OpenAI. */
21
+ export declare function runChatGptSignOut(opts?: ChatGptCliOptions): Promise<number>;
22
+ /** Where the sign-in is, and whether it carries OpenAI ids here. */
23
+ export declare function runChatGptStatus(opts?: ChatGptCliOptions): number;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * lib/chatgpt/cli.ts — the terminal side of Sign in with ChatGPT (ADR 0126),
3
+ * shared by `melchizedek-setup --chatgpt-signin | --chatgpt-signout |
4
+ * --chatgpt-status` and `melchizedek-chat --chatgpt-signin`.
5
+ *
6
+ * Prints the authorization link (it carries no secret: a one-time state and
7
+ * a PKCE challenge), the file the credential lives in, and what the sign-in
8
+ * now carries. Never a token.
9
+ */
10
+ import { signIn, signOut } from './oauth.js';
11
+ import { CHATGPT_SIGNIN_ENV, chatGptSignInRoutesOpenAi, chatGptSignInStatus, servedSurface, signInFile } from './state.js';
12
+ /** The browser sign-in, from a terminal. Returns an exit code. */
13
+ export async function runChatGptSignIn(opts = {}) {
14
+ const log = opts.log ?? ((l) => console.log(l));
15
+ const env = opts.env ?? process.env;
16
+ const surface = servedSurface();
17
+ if (surface) {
18
+ log(`✗ ${surface} is a served surface; Sign in with ChatGPT is for your own machine only.`);
19
+ return 1;
20
+ }
21
+ const file = signInFile(env);
22
+ try {
23
+ await signIn({
24
+ file,
25
+ ...(opts.issuer ? { issuer: opts.issuer } : {}),
26
+ ...(opts.port !== undefined ? { port: opts.port } : {}),
27
+ ...(opts.openBrowser ? { openBrowser: opts.openBrowser } : {}),
28
+ ...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
29
+ onUrl: (url) => log(`Opening your browser to sign in with ChatGPT. If it does not open, visit:\n\n ${url}\n`),
30
+ });
31
+ log(`✓ Signed in with ChatGPT; your plan may be used for OpenAI requests (stored at ${file}, mode 600).`);
32
+ log(chatGptSignInRoutesOpenAi(env)
33
+ ? ' OpenAI model ids (gpt-*, o<digit>*) now run on it on this machine.'
34
+ : env[CHATGPT_SIGNIN_ENV]?.trim().toLowerCase() === 'off'
35
+ ? ` ${CHATGPT_SIGNIN_ENV}=off is set, so nothing uses it until you unset it.`
36
+ : ' OPENAI_API_KEY (or another OpenAI endpoint) is set and wins; unset it to run OpenAI ids on the sign-in.');
37
+ log(' Local only: melchizedek-serve and melchizedek-worker refuse to start while it is the OpenAI path. Usage limits are your ChatGPT plan\'s.');
38
+ return 0;
39
+ }
40
+ catch (err) {
41
+ log(`✗ ${err instanceof Error ? err.message : String(err)}`);
42
+ return 1;
43
+ }
44
+ }
45
+ /** Sign out: tokens removed locally, refresh token revoked at OpenAI. */
46
+ export async function runChatGptSignOut(opts = {}) {
47
+ const log = opts.log ?? ((l) => console.log(l));
48
+ const file = signInFile(opts.env ?? process.env);
49
+ try {
50
+ const r = await signOut(file, opts.issuer ? { issuer: opts.issuer } : {});
51
+ if (!r.hadTokens)
52
+ log(`· No ChatGPT sign-in was stored at ${file}.`);
53
+ else if (r.revoked)
54
+ log(`✓ Signed out: the tokens are removed from ${file} and revoked at OpenAI.`);
55
+ else
56
+ log(`✓ Signed out locally (${file}). OpenAI did not confirm the revocation: disconnect the app in ChatGPT's settings.`);
57
+ return 0;
58
+ }
59
+ catch (err) {
60
+ log(`✗ ${err instanceof Error ? err.message : String(err)}`);
61
+ return 1;
62
+ }
63
+ }
64
+ /** Where the sign-in is, and whether it carries OpenAI ids here. */
65
+ export function runChatGptStatus(opts = {}) {
66
+ const log = opts.log ?? ((l) => console.log(l));
67
+ const env = opts.env ?? process.env;
68
+ const s = chatGptSignInStatus(env);
69
+ if (s.problem) {
70
+ log(`✗ ${s.problem}`);
71
+ return 1;
72
+ }
73
+ log(`${s.signedIn ? '✓ signed in' : '· not signed in'} · ${s.file}${s.enabled ? '' : ` · ${CHATGPT_SIGNIN_ENV}=off`}`);
74
+ if (s.signedIn)
75
+ log(chatGptSignInRoutesOpenAi(env) ? ' carries OpenAI ids on this machine (local only)' : ' not used here: OPENAI_API_KEY or another OpenAI endpoint wins, or it is switched off');
76
+ return 0;
77
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * lib/chatgpt/oauth.ts — OpenAI's Sign in with ChatGPT, plan-usage flow, for a
3
+ * local app (ADR 0126).
4
+ *
5
+ * Implemented from OpenAI's documentation of the flow for open-source apps
6
+ * that run on the user's machine:
7
+ * https://developers.openai.com/siwc/token-sharing-open-source
8
+ * https://developers.openai.com/siwc/token-sharing-open-source/sign-in
9
+ * https://developers.openai.com/siwc/token-sharing-open-source/profiles-and-sessions
10
+ * https://developers.openai.com/siwc/token-sharing-open-source/token-reference
11
+ * https://developers.openai.com/siwc/token-sharing-open-source/errors-and-recovery
12
+ *
13
+ * THE FLOW: an authorization-code grant with PKCE (S256), a fresh state and
14
+ * nonce per attempt, and a loopback redirect on http://127.0.0.1:<port>/auth/callback.
15
+ * The app registers itself dynamically: the first authorization sends
16
+ * `client_id=dynamic_agent_client` with an `agent_name_hint`, and the callback
17
+ * returns the client id OpenAI issued, which is kept for every later sign-in
18
+ * and refresh. No client secret, no pre-registration, and never another
19
+ * app's client id or stored token. The token request names the resource
20
+ * `https://api.openai.com/v1`; the ID token is verified (signature against
21
+ * the issuer's JWKS, iss, aud = the issued client id, exp, nonce) before
22
+ * anything is saved, and the plan scope must be granted.
23
+ *
24
+ * TOKENS: an access token lasts an hour and the refresh token rotates on every
25
+ * refresh. Refreshes are serialized through a lock file (lib/chatgpt/store.ts)
26
+ * and an unusable refresh token clears the tokens and asks for a new sign-in.
27
+ * No token is ever logged or put in an error message.
28
+ */
29
+ export declare const OPENAI_AUTH_ISSUER = "https://auth.openai.com";
30
+ /** The resource the tokens are for: the public API, where the Responses endpoint lives. */
31
+ export declare const OPENAI_API_RESOURCE = "https://api.openai.com/v1";
32
+ export declare const SIGNIN_SCOPES = "openid profile email offline_access resource.invoke chatgpt.tokens.use.direct";
33
+ export declare const CALLBACK_PATH = "/auth/callback";
34
+ /** The client id an app sends before OpenAI has issued it one. */
35
+ export declare const DYNAMIC_CLIENT_ID = "dynamic_agent_client";
36
+ export declare const APP_NAME = "Melchizedek";
37
+ /** A sign-in failure: a code for the program, a message for the person, never a token. */
38
+ export declare class SignInError extends Error {
39
+ readonly code: string;
40
+ constructor(code: string, message: string);
41
+ }
42
+ export interface OAuthOptions {
43
+ /** The authorization server. Default https://auth.openai.com; an http issuer is accepted on loopback only (tests). */
44
+ issuer?: string;
45
+ }
46
+ interface Discovery {
47
+ issuer: string;
48
+ authorization_endpoint: string;
49
+ token_endpoint: string;
50
+ jwks_uri: string;
51
+ revocation_endpoint?: string;
52
+ }
53
+ /** The issuer's OpenID configuration; every endpoint must be on the issuer's own origin. */
54
+ export declare function discover(opts?: OAuthOptions): Promise<Discovery>;
55
+ export declare const pkceChallenge: (verifier: string) => string;
56
+ /** Open a URL in the default browser, with no shell. */
57
+ export declare function openInBrowser(url: string): Promise<void>;
58
+ export interface SignInOptions extends OAuthOptions {
59
+ /** The credential file (lib/chatgpt/state.ts signInFile). */
60
+ file: string;
61
+ /** The loopback port; 0 (the default) takes a free one. */
62
+ port?: number;
63
+ /** Opens the authorization URL; default the system browser. */
64
+ openBrowser?: (url: string) => Promise<void>;
65
+ /** Called with the authorization URL before the browser opens, so the person can open it themselves. */
66
+ onUrl?: (url: string) => void;
67
+ timeoutMs?: number;
68
+ signal?: AbortSignal;
69
+ }
70
+ /** What a sign-in reports: never a token. */
71
+ export interface SignInResult {
72
+ subject: string;
73
+ scopes: string[];
74
+ expiresAt: number;
75
+ planUsage: boolean;
76
+ }
77
+ /**
78
+ * The browser sign-in. Stores the registration (host id, issued client id)
79
+ * before the code exchange, and the tokens after the ID token verifies and
80
+ * the plan scope is confirmed.
81
+ */
82
+ export declare function signIn(opts: SignInOptions): Promise<SignInResult>;
83
+ export interface AccessTokenOptions extends OAuthOptions {
84
+ now?: () => number;
85
+ }
86
+ /**
87
+ * A usable access token from the credential file, refreshed when it is within
88
+ * two minutes of expiry (and past `earliest_refresh_at`). Refreshes take the
89
+ * file's lock and re-read it first, so a refresh another process just made is
90
+ * used rather than repeated.
91
+ */
92
+ export declare function freshAccessToken(file: string, opts?: AccessTokenOptions): Promise<string>;
93
+ /**
94
+ * Sign out: the tokens leave the file (the host id and issued client id
95
+ * stay, for the next sign-in), and the refresh token is revoked at the
96
+ * issuer. `revoked: false` means the local tokens are gone but the remote
97
+ * revocation was not confirmed: disconnect the app in ChatGPT's settings.
98
+ */
99
+ export declare function signOut(file: string, opts?: OAuthOptions): Promise<{
100
+ hadTokens: boolean;
101
+ revoked: boolean;
102
+ }>;
103
+ export {};