melchizedek-agents 1.3.0 → 1.3.1

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/CHANGELOG.md CHANGED
@@ -6,6 +6,39 @@ the starter pack and the templates), not the repo's full history.
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ ## 1.3.1 — 2026-10-11
10
+
11
+ A patch for Sign in with ChatGPT, from its first live run on a ChatGPT plan:
12
+ turns on the sign-in now answer, a first sign-in keeps one host id, and a
13
+ plan without plan usage for apps is named as the reason a sign-in is
14
+ refused. Bin paths are written as npm stores them.
15
+
16
+ ### Fixed
17
+
18
+ - **Sign in with ChatGPT answers again: a stream whose `response.completed`
19
+ carries an empty `output` builds its final from `response.output_item.done`.**
20
+ The plan-usage route (`store: false`) sends every finished item only as
21
+ `output_item.done` and closes with an empty `output`, so on 1.3.0 a turn on
22
+ the sign-in completed with no text and no tool calls. The Responses stream
23
+ now keeps each finished item by `output_index` and uses them when the
24
+ completed reply has none; a populated `output` (every keyed call) still
25
+ wins. Found in the first live run on a ChatGPT plan (`gpt-5.5`,
26
+ `gpt-6.1-sol`).
27
+ - **Bin paths are written as npm stores them** (`dist/scripts/<bin>.js`, no
28
+ leading `./`). Same bin names and targets; npm 11 no longer warns at publish
29
+ that each bin "was invalid and removed".
30
+ - **Sign in with ChatGPT keeps one host id from the first attempt.** The
31
+ installation's `ext_agent_host_id` is written to the credential file (mode
32
+ 600) before the browser opens, as OpenAI's docs ask, so a first sign-in that
33
+ times out or is cancelled no longer sends a new host id on the retry. A
34
+ stored registration is never overwritten.
35
+ - **A refused ChatGPT sign-in says why.** `invalid_grant` on the
36
+ authorization-code exchange, and `access_denied` (or another refusal) at
37
+ the consent page, now print one line: the sign-in was refused, the most
38
+ common cause is a ChatGPT plan or workspace that does not include plan
39
+ usage for apps, and how to retry. The error codes are unchanged; a
40
+ refresh's `invalid_grant` still means sign in again.
41
+
9
42
  ## 1.3.0 — 2026-10-10
10
43
 
11
44
  Release 1.3.0 connects syndicates to the MCP ecosystem in both directions
@@ -75,9 +75,10 @@ export interface SignInResult {
75
75
  planUsage: boolean;
76
76
  }
77
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.
78
+ * The browser sign-in. Stores the host id before the browser opens when no
79
+ * file exists yet, the registration (host id, issued client id) before the
80
+ * code exchange, and the tokens after the ID token verifies and the plan
81
+ * scope is confirmed.
81
82
  */
82
83
  export declare function signIn(opts: SignInOptions): Promise<SignInResult>;
83
84
  export interface AccessTokenOptions extends OAuthOptions {
@@ -50,6 +50,14 @@ const UNUSABLE_REFRESH = new Set([
50
50
  'refresh_token_invalidated',
51
51
  'refresh_token_reused',
52
52
  ]);
53
+ /**
54
+ * Callback error codes that mean the consent page refused or the person
55
+ * stopped there: OpenAI's page for a plan or workspace without plan usage for
56
+ * apps offers no way on, so leaving it is the usual way these arrive.
57
+ */
58
+ const REFUSED_AT_CONSENT = new Set(['access_denied', 'unauthorized_client', 'consent_required', 'interaction_required']);
59
+ /** One line for a sign-in OpenAI refused at the consent page or the code exchange. */
60
+ const signInRefused = (code) => `ChatGPT refused the sign-in (${code}); the most common cause is a ChatGPT plan or workspace that does not include plan usage for apps. Retry with \`npm run setup -- --chatgpt-signin\` (\`npx melchizedek-setup --chatgpt-signin\` from an installed package).`;
53
61
  /** A sign-in failure: a code for the program, a message for the person, never a token. */
54
62
  export class SignInError extends Error {
55
63
  code;
@@ -236,7 +244,7 @@ async function listen(port, state, savedClientId, timeoutMs, signal) {
236
244
  res.writeHead(200, PAGE_HEADERS).end(page('Sign-in cancelled', 'You can close this tab and return to the terminal.'));
237
245
  const raw = url.searchParams.get('error') ?? '';
238
246
  const code = /^[a-z0-9_.-]{1,64}$/i.test(raw) ? raw : 'authorization_failed';
239
- settle(new SignInError(code, `ChatGPT did not authorize the sign-in (${code}).`));
247
+ settle(new SignInError(code, REFUSED_AT_CONSENT.has(code) ? signInRefused(code) : `ChatGPT did not authorize the sign-in (${code}).`));
240
248
  return;
241
249
  }
242
250
  const codes = url.searchParams.getAll('code');
@@ -284,9 +292,10 @@ export async function openInBrowser(url) {
284
292
  });
285
293
  }
286
294
  /**
287
- * The browser sign-in. Stores the registration (host id, issued client id)
288
- * before the code exchange, and the tokens after the ID token verifies and
289
- * the plan scope is confirmed.
295
+ * The browser sign-in. Stores the host id before the browser opens when no
296
+ * file exists yet, the registration (host id, issued client id) before the
297
+ * code exchange, and the tokens after the ID token verifies and the plan
298
+ * scope is confirmed.
290
299
  */
291
300
  export async function signIn(opts) {
292
301
  const issuer = issuerOf(opts);
@@ -295,6 +304,11 @@ export async function signIn(opts) {
295
304
  const sameIssuer = previous?.issuer === issuer;
296
305
  const hostId = previous?.hostId ?? `urn:uuid:${randomUUID()}`;
297
306
  const savedClientId = sameIssuer ? previous?.clientId : undefined;
307
+ // OpenAI asks for the host id to be chosen and persisted before the first
308
+ // sign-in, so a timed-out or cancelled attempt does not send a new one next
309
+ // time. Only written when nothing is stored: a stored registration is kept.
310
+ if (!previous)
311
+ writeStoredSignIn(opts.file, { version: 1, issuer, hostId });
298
312
  const state = randomValue();
299
313
  const nonce = randomValue();
300
314
  const verifier = randomValue();
@@ -326,14 +340,24 @@ export async function signIn(opts) {
326
340
  ? { ...previous, clientId: cb.clientId, hostId }
327
341
  : { version: 1, issuer, hostId, clientId: cb.clientId };
328
342
  writeStoredSignIn(opts.file, registration);
329
- const data = await tokenRequest(d.token_endpoint, {
330
- grant_type: 'authorization_code',
331
- client_id: cb.clientId,
332
- code: cb.code,
333
- code_verifier: verifier,
334
- redirect_uri: listener.redirectUri,
335
- resource: OPENAI_API_RESOURCE,
336
- });
343
+ let data;
344
+ try {
345
+ data = await tokenRequest(d.token_endpoint, {
346
+ grant_type: 'authorization_code',
347
+ client_id: cb.clientId,
348
+ code: cb.code,
349
+ code_verifier: verifier,
350
+ redirect_uri: listener.redirectUri,
351
+ resource: OPENAI_API_RESOURCE,
352
+ });
353
+ }
354
+ catch (err) {
355
+ // OpenAI's consent page refuses a plan or workspace without plan usage
356
+ // for apps, and the exchange then answers invalid_grant.
357
+ if (err instanceof SignInError && err.code === 'invalid_grant')
358
+ throw new SignInError('invalid_grant', signInRefused('invalid_grant'));
359
+ throw err;
360
+ }
337
361
  const subject = await verifyIdToken(data.id_token, d, cb.clientId, nonce);
338
362
  const fields = tokenFields(data);
339
363
  const planUsage = fields.scopes.includes(PLAN_SCOPE);
@@ -755,6 +755,10 @@ export class GptAdapter {
755
755
  let completed;
756
756
  let streamedThoughts = false;
757
757
  const text = [];
758
+ // Each finished output item, by its index. The plan-usage route (Sign in
759
+ // with ChatGPT, store: false) sends response.completed with an empty
760
+ // output: the items arrive only here, so the final is built from them.
761
+ const doneItems = [];
758
762
  for await (const ev of untilAborted(events, signal)) {
759
763
  const delta = streamEventDelta(ev);
760
764
  if (delta) {
@@ -765,7 +769,10 @@ export class GptAdapter {
765
769
  yield { partial: true, parts: [delta.thought ? { type: 'thinking', text: delta.text } : { type: 'text', text: delta.text }] };
766
770
  continue;
767
771
  }
768
- if (ev?.type === 'response.completed' && ev.response) {
772
+ if (ev?.type === 'response.output_item.done' && ev.item && typeof ev.item === 'object') {
773
+ doneItems.push({ index: typeof ev.output_index === 'number' ? ev.output_index : doneItems.length, item: ev.item });
774
+ }
775
+ else if (ev?.type === 'response.completed' && ev.response) {
769
776
  completed = ev.response;
770
777
  }
771
778
  else if (ev?.type === 'response.failed' || ev?.type === 'error') {
@@ -775,6 +782,9 @@ export class GptAdapter {
775
782
  }
776
783
  }
777
784
  if (completed) {
785
+ if ((!Array.isArray(completed.output) || completed.output.length === 0) && doneItems.length > 0) {
786
+ completed = { ...completed, output: doneItems.sort((x, y) => x.index - y.index).map((d) => d.item) };
787
+ }
778
788
  const { thinking, final } = this.finalOf(completed, model, { skipThoughts: streamedThoughts });
779
789
  if (thinking)
780
790
  yield thinking;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "melchizedek-agents",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "A multi-model, multi-agent orchestration framework — YAML-defined syndicates on the engine's own agent loop, Supabase or Postgres session persistence, pgvector long-term memory. Companion repo to the lyceumagents.com curriculum.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -48,17 +48,17 @@
48
48
  "./package.json": "./package.json"
49
49
  },
50
50
  "bin": {
51
- "melchizedek-serve": "./dist/scripts/a2a_server.js",
52
- "melchizedek-mcp": "./dist/scripts/mcp_server.js",
53
- "melchizedek-chat": "./dist/scripts/syndicate_chat.js",
54
- "melchizedek-doctor": "./dist/scripts/doctor.js",
55
- "melchizedek-skills": "./dist/scripts/skills_install.js",
56
- "melchizedek-worker": "./dist/scripts/assistant_worker.js",
57
- "melchizedek-db": "./dist/scripts/db.js",
58
- "melchizedek-init": "./dist/scripts/init.js",
59
- "melchizedek-setup": "./dist/scripts/setup.js",
60
- "melchizedek-registry": "./dist/scripts/registry.js",
61
- "melchizedek-codemod": "./dist/scripts/yaml_codemod.js"
51
+ "melchizedek-serve": "dist/scripts/a2a_server.js",
52
+ "melchizedek-mcp": "dist/scripts/mcp_server.js",
53
+ "melchizedek-chat": "dist/scripts/syndicate_chat.js",
54
+ "melchizedek-doctor": "dist/scripts/doctor.js",
55
+ "melchizedek-skills": "dist/scripts/skills_install.js",
56
+ "melchizedek-worker": "dist/scripts/assistant_worker.js",
57
+ "melchizedek-db": "dist/scripts/db.js",
58
+ "melchizedek-init": "dist/scripts/init.js",
59
+ "melchizedek-setup": "dist/scripts/setup.js",
60
+ "melchizedek-registry": "dist/scripts/registry.js",
61
+ "melchizedek-codemod": "dist/scripts/yaml_codemod.js"
62
62
  },
63
63
  "files": [
64
64
  "dist",