@steve31415/baselib 3.15.0 → 3.17.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/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # baselib
2
2
 
3
- The Plasticine new-world shared platform library (`@steve31415/baselib`).
3
+ The Plasticine fleet's shared platform library (`@steve31415/baselib`).
4
4
  What it provides and why: `docs/SPEC.md`. How it's put together:
5
5
  `docs/IMPL.md`. Full design rationale with alternatives:
6
- `~/migration/research/base-services-design.md` (step 5).
6
+ `~/migration/research/base-services-design.md`.
7
7
 
8
8
  Server subpath exports: `config`, `log`, `auth`, `s2s`, `db`, `http`, `sync`,
9
9
  `app-update`, `llm`, `email`, and `twitter`. Browser exports: `log-browser`, `rum`,
@@ -34,17 +34,15 @@ deploy (`~/plasticine-way/docs/OPERATIONS.md`, "Deployment safety").
34
34
  first step of any production investigation (same doc, "Investigating
35
35
  production state").
36
36
 
37
- `twitter` provides Mirror2-first tweet lookup, TwitterAPI.io batch/search/
37
+ `twitter` provides Mirror-first tweet lookup, TwitterAPI.io batch/search/
38
38
  article calls, and thread assembly through direct reply edges. It includes
39
39
  one level of quotes, article bodies and URL expansions. Use one shared
40
40
  `MirrorTweetClient(mirrorUrl, serviceFetch)` and `TwitterApiIoClient(key)`
41
41
  per run; `assembleThread(id, { api, mirror, ancestors: 'all' })` returns
42
42
  role-tagged tweets, source counts and warnings. Batch prefetching Mirror
43
- ids seeds the shared cache. Reader2 and Snip retain their copies for now.
43
+ ids seeds the shared cache. Reader and Snip retain their copies for now.
44
44
 
45
45
  Releasing: bump `version`, commit, and run `npm run deploy` (Coder runs it
46
46
  automatically when integrating a baselib branch) — it pushes the `v<version>`
47
47
  tag, whose workflow gates on verify and publishes via OIDC, and waits for the
48
48
  registry (`~/plasticine-way/docs/OPERATIONS.md`, "Publishing baselib").
49
-
50
- Reflects Plasticine Way commit: see `docs/IMPL.md` footer.
package/dist/auth.d.ts CHANGED
@@ -6,7 +6,7 @@ export declare const SESSION_COOKIE = "pw_session";
6
6
  /** The `code` in a 403 JSON body when a signed-in user is refused by the
7
7
  * app's userAccess policy (as opposed to any other 403). */
8
8
  export declare const ACCESS_DENIED_CODE = "app_access_denied";
9
- /** A user's fleet role, assigned in auth2's users table. */
9
+ /** A user's fleet role, assigned in auth's users table. */
10
10
  export type UserRole = 'owner' | 'invited';
11
11
  /** Which signed-in users an app admits. 'owner' (the default): only the
12
12
  * fleet owner(s). 'invited': any active fleet user. */
@@ -39,7 +39,7 @@ export interface RequireIdentityOptions extends S2sOptions {
39
39
  missMs?: number;
40
40
  };
41
41
  /** Which signed-in users this app admits. Default 'owner': only users
42
- * whose auth2 role is owner. Pass 'invited' only for an app that is
42
+ * whose auth role is owner. Pass 'invited' only for an app that is
43
43
  * multi-user by design, and say so in its SPEC. Applies to browser
44
44
  * sessions and the test bypass alike; service callers are governed by
45
45
  * grants, not by this. */
package/dist/auth.js CHANGED
@@ -12,7 +12,7 @@
12
12
  // in-instance (10 min hits / 30 s misses — the old-world numbers).
13
13
  //
14
14
  // Then the user-access policy: by default an app admits only users whose
15
- // auth2 role is 'owner'; `userAccess: 'invited'` admits any active fleet
15
+ // auth role is 'owner'; `userAccess: 'invited'` admits any active fleet
16
16
  // user. Being on the fleet sign-in allowlist never grants access by itself,
17
17
  // so a port that forgets the question is closed, not open. Service callers
18
18
  // are governed by grants, not by this policy.
package/dist/db.d.ts CHANGED
@@ -111,7 +111,7 @@ export declare const FLEET_POOL_DEFAULTS: {
111
111
  * rule: an advisory lock is not mutual exclusion. Our locks limit
112
112
  * duplicated effort; code under one must be safe to overlap, and long
113
113
  * work should probe the lock connection periodically (`select 1`) and
114
- * stop on failure (Plasticine Way `docs/DATABASE.md`; mirror2's ingestion
114
+ * stop on failure (Plasticine Way `docs/DATABASE.md`; mirror's ingestion
115
115
  * loop is the reference).
116
116
  *
117
117
  * `createPool` attaches all of this; exported for a pool an app built
package/dist/db.js CHANGED
@@ -117,7 +117,7 @@ const instrumented = new WeakSet();
117
117
  * rule: an advisory lock is not mutual exclusion. Our locks limit
118
118
  * duplicated effort; code under one must be safe to overlap, and long
119
119
  * work should probe the lock connection periodically (`select 1`) and
120
- * stop on failure (Plasticine Way `docs/DATABASE.md`; mirror2's ingestion
120
+ * stop on failure (Plasticine Way `docs/DATABASE.md`; mirror's ingestion
121
121
  * loop is the reference).
122
122
  *
123
123
  * `createPool` attaches all of this; exported for a pool an app built
@@ -3,7 +3,7 @@ export declare const SHA40: RegExp;
3
3
  /** Lock staleness is judged from the GCS object's server-side timeCreated,
4
4
  * never from anything the lock writer wrote (its clock may be wrong). */
5
5
  export declare function lockIsStale(timeCreatedIso: string, nowMs: number, staleMs: number): boolean;
6
- /** Consistent serving base across an app's services (notes2's rule,
6
+ /** Consistent serving base across an app's services (notes's rule,
7
7
  * generalized): all services on one SHA -> that SHA; some already on the
8
8
  * target -> the rest must agree on one other SHA (the base); anything else
9
9
  * is a skew that needs a human. A null buildSha (pre-unification revision)
@@ -11,7 +11,7 @@ export function lockIsStale(timeCreatedIso, nowMs, staleMs) {
11
11
  return false;
12
12
  return nowMs - created > staleMs;
13
13
  }
14
- /** Consistent serving base across an app's services (notes2's rule,
14
+ /** Consistent serving base across an app's services (notes's rule,
15
15
  * generalized): all services on one SHA -> that SHA; some already on the
16
16
  * target -> the rest must agree on one other SHA (the base); anything else
17
17
  * is a skew that needs a human. A null buildSha (pre-unification revision)
@@ -51,7 +51,7 @@ export interface DeployConfig {
51
51
  * runtime-config change is a commit the deploy carries rather than a hand-run
52
52
  * `gcloud run services update`. Undeclared vars are left as they are. */
53
53
  updateEnv?: Record<string, string>;
54
- /** Refuse to deploy across a migrations/ diff (notes2 semantics). */
54
+ /** Refuse to deploy across a migrations/ diff (notes semantics). */
55
55
  refuseMigrationDiffOnDeploy?: boolean;
56
56
  /** Repo-relative path of an optional ES module exporting hooks. */
57
57
  hooks?: string;
@@ -114,10 +114,10 @@ export interface DeployHooks {
114
114
  publishAssets?: (ctx: HookContext) => Promise<void>;
115
115
  /** Runs after all gates pass, immediately before the first traffic switch. */
116
116
  preRelease?: (ctx: HookContext) => Promise<void>;
117
- /** Runs right after traffic moves (e.g. todo2's announce-build). */
117
+ /** Runs right after traffic moves (e.g. todo's announce-build). */
118
118
  postTraffic?: (ctx: HookContext) => Promise<void>;
119
119
  /** Extra smoke checks after the shared health checks. */
120
120
  smoke?: (ctx: HookContext) => Promise<void>;
121
- /** Runs after everything else succeeded (e.g. notes2's release state). */
121
+ /** Runs after everything else succeeded (e.g. notes's release state). */
122
122
  postSuccess?: (ctx: HookContext) => Promise<void>;
123
123
  }
@@ -66,6 +66,7 @@ export async function llmComplete(request) {
66
66
  temperature,
67
67
  supportsTemperature: modelEntry.supportsTemperature ?? true,
68
68
  supportsThinkingBudgetZero: modelEntry.supportsThinkingBudgetZero ?? true,
69
+ supportsForcedToolChoice: modelEntry.supportsForcedToolChoice ?? true,
69
70
  effort: modelEntry.supportsEffort ? request.effort : undefined,
70
71
  jsonSchema: request.jsonSchema,
71
72
  apiKey,
@@ -40,6 +40,15 @@ export interface ModelEntry {
40
40
  * when the caller's output budget leaves no meaningful headroom.
41
41
  */
42
42
  thinksByDefault?: boolean;
43
+ /**
44
+ * Whether this model accepts a forced `tool_choice` (`{type: 'tool'}`),
45
+ * which the Anthropic adapter uses to get `jsonSchema` output. Defaults to
46
+ * `true`. Opus 5.5 rejects it with a 400 ("tool_choice: type \"tool\" and
47
+ * \"any\" are not supported for this model.", verified 2026-09-22); for
48
+ * such models the adapter requests structured output via
49
+ * `output_config.format` instead.
50
+ */
51
+ supportsForcedToolChoice?: boolean;
43
52
  }
44
53
  /**
45
54
  * Output budget below which a `thinksByDefault` model is considered at risk of
@@ -10,7 +10,7 @@ export const THINKING_HEADROOM_WARN_TOKENS = 4096;
10
10
  */
11
11
  export const MODEL_REGISTRY = {
12
12
  // 2026-08 suite refresh: current models are claude-sonnet-5 / claude-opus-5
13
- // (Anthropic), the gpt-5.6 sol/terra/luna family (OpenAI), and the Gemini
13
+ // (Anthropic; claude-opus-5-5 succeeded claude-opus-5 on 2026-09-22), the gpt-5.6 sol/terra/luna family (OpenAI), and the Gemini
14
14
  // 3.5/3.6 GA line plus gemini-3.1-pro-preview (Google). Older names stay
15
15
  // registered so existing callers keep working, but no fallback chain routes
16
16
  // through a retiring model: gemini-2.5-pro and gemini-2.5-flash shut down
@@ -106,6 +106,20 @@ export const MODEL_REGISTRY = {
106
106
  fallbacks: ['claude-sonnet-5', 'gemini-3.1-pro-preview'],
107
107
  },
108
108
  // --- Top tier ---
109
+ 'claude-opus-5-5': {
110
+ provider: 'anthropic',
111
+ supportsEffort: true,
112
+ apiModelId: 'claude-opus-5-5',
113
+ fallbacks: ['gpt-5.6-sol', 'gemini-3.1-pro-preview'],
114
+ // All verified live 2026-09-22: rejects `temperature` and forced
115
+ // tool_choice (400 each); thinking is always on and its tokens count
116
+ // against `maxTokens`. With no `effort` passed the API runs at `medium`
117
+ // (Opus 5's default was `high`) — deliberately left as is (Steve,
118
+ // 2026-09-22: medium suits 5.5's capabilities).
119
+ supportsTemperature: false,
120
+ supportsForcedToolChoice: false,
121
+ thinksByDefault: true,
122
+ },
109
123
  'claude-opus-5': {
110
124
  provider: 'anthropic',
111
125
  supportsEffort: true,
@@ -136,14 +150,14 @@ export const MODEL_REGISTRY = {
136
150
  'gpt-5.6-sol': {
137
151
  provider: 'openai',
138
152
  apiModelId: 'gpt-5.6-sol',
139
- fallbacks: ['claude-opus-5', 'gemini-3.1-pro-preview'],
153
+ fallbacks: ['claude-opus-5-5', 'gemini-3.1-pro-preview'],
140
154
  supportsTemperature: false,
141
155
  thinksByDefault: true,
142
156
  },
143
157
  'gpt-5.2': {
144
158
  provider: 'openai',
145
159
  apiModelId: 'gpt-5.2',
146
- fallbacks: ['claude-opus-5', 'gemini-3.1-pro-preview'],
160
+ fallbacks: ['claude-opus-5-5', 'gemini-3.1-pro-preview'],
147
161
  },
148
162
  // Google's best Pro model as of 2026-08 — still Preview-labeled (no GA
149
163
  // Gemini 3.x Pro exists), but the only Pro-class option once 2.5-pro
@@ -151,7 +165,7 @@ export const MODEL_REGISTRY = {
151
165
  'gemini-3.1-pro-preview': {
152
166
  provider: 'gemini',
153
167
  apiModelId: 'gemini-3.1-pro-preview',
154
- fallbacks: ['claude-opus-5', 'gpt-5.6-sol'],
168
+ fallbacks: ['claude-opus-5-5', 'gpt-5.6-sol'],
155
169
  // Thinks by default (thinking_level defaults to `high`).
156
170
  thinksByDefault: true,
157
171
  // Rejects thinkingBudget 0 (400, verified 2026-08-09).
@@ -161,6 +175,6 @@ export const MODEL_REGISTRY = {
161
175
  'gemini-2.5-pro': {
162
176
  provider: 'gemini',
163
177
  apiModelId: 'gemini-2.5-pro',
164
- fallbacks: ['claude-opus-5', 'gpt-5.6-sol'],
178
+ fallbacks: ['claude-opus-5-5', 'gpt-5.6-sol'],
165
179
  },
166
180
  };
@@ -12,6 +12,51 @@ function classifyFinish(stopReason) {
12
12
  default: return 'other';
13
13
  }
14
14
  }
15
+ /**
16
+ * Keywords `output_config.format` rejects outright with a 400 (verified on
17
+ * Opus 5.5, 2026-09-22). `minItems` is also rejected for values other than 0
18
+ * or 1, handled separately; `minLength`/`maxLength`, `pattern` and `format`
19
+ * are accepted.
20
+ */
21
+ const UNSUPPORTED_FORMAT_KEYWORDS = [
22
+ 'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf',
23
+ 'maxItems', 'uniqueItems',
24
+ ];
25
+ /**
26
+ * Transform a portable JSON schema into the dialect Anthropic's structured
27
+ * outputs (`output_config.format`) accepts: every object schema must set
28
+ * `additionalProperties: false`, and the numeric/array bounds listed above are
29
+ * dropped. The model then no longer enforces those bounds — callers validate
30
+ * the parsed result as they already must for truncation. Unlike forced
31
+ * tool_use, optional properties stay optional. Returns a deep copy — the
32
+ * caller's schema object is reused across failover attempts.
33
+ */
34
+ function toOutputFormatSchema(node) {
35
+ if (Array.isArray(node))
36
+ return node.map(toOutputFormatSchema);
37
+ if (node === null || typeof node !== 'object')
38
+ return node;
39
+ const out = {};
40
+ for (const [key, value] of Object.entries(node)) {
41
+ if (UNSUPPORTED_FORMAT_KEYWORDS.includes(key))
42
+ continue;
43
+ if (key === 'minItems' && typeof value === 'number' && value > 1)
44
+ continue;
45
+ // Maps whose values are schemas (not keywords): recurse per entry so a
46
+ // property that happens to be named `minimum` is kept.
47
+ if ((key === 'properties' || key === '$defs' || key === 'definitions')
48
+ && value !== null && typeof value === 'object' && !Array.isArray(value)) {
49
+ out[key] = Object.fromEntries(Object.entries(value).map(([k, v]) => [k, toOutputFormatSchema(v)]));
50
+ continue;
51
+ }
52
+ out[key] = toOutputFormatSchema(value);
53
+ }
54
+ const type = out.type;
55
+ if (type === 'object' || (Array.isArray(type) && type.includes('object'))) {
56
+ out.additionalProperties = false;
57
+ }
58
+ return out;
59
+ }
15
60
  export const anthropicProvider = {
16
61
  name: 'anthropic',
17
62
  async complete(request) {
@@ -26,7 +71,7 @@ export const anthropicProvider = {
26
71
  max_tokens: request.maxTokens,
27
72
  messages: [{ role: 'user', content: request.prompt }],
28
73
  };
29
- // Some models (Opus 4.7+, Sonnet 5, Fable 5) reject `temperature`
74
+ // Some models (Opus 4.7+, Sonnet 5, Fable 5, Opus 5.5) reject `temperature`
30
75
  // outright with a 400 — omit the field entirely rather than sending it.
31
76
  if (request.supportsTemperature) {
32
77
  body.temperature = request.temperature;
@@ -42,8 +87,16 @@ export const anthropicProvider = {
42
87
  ? [{ type: 'text', text: request.systemPrompt, cache_control: { type: 'ephemeral' } }]
43
88
  : request.systemPrompt;
44
89
  }
45
- // JSON schema: use tool_use to get structured output
46
- if (request.jsonSchema) {
90
+ // JSON schema: a forced tool_use call where the model allows it, else
91
+ // structured outputs (Opus 5.5 rejects forced tool_choice).
92
+ const useOutputFormat = request.jsonSchema !== undefined && !request.supportsForcedToolChoice;
93
+ if (request.jsonSchema && useOutputFormat) {
94
+ body.output_config = {
95
+ ...body.output_config,
96
+ format: { type: 'json_schema', schema: toOutputFormatSchema(request.jsonSchema.schema) },
97
+ };
98
+ }
99
+ else if (request.jsonSchema) {
47
100
  body.tools = [{
48
101
  name: request.jsonSchema.name,
49
102
  description: `Generate structured output matching the ${request.jsonSchema.name} schema`,
@@ -103,7 +156,18 @@ export const anthropicProvider = {
103
156
  const finishReason = data.stop_reason ?? undefined;
104
157
  let text;
105
158
  let json;
106
- if (request.jsonSchema) {
159
+ if (useOutputFormat) {
160
+ text = data.content?.find((c) => c.type === 'text')?.text || '';
161
+ try {
162
+ json = text ? JSON.parse(text) : undefined;
163
+ }
164
+ catch {
165
+ // Truncated or malformed body: surface as json undefined, like the
166
+ // tool_use path, so the caller's truncation handling applies.
167
+ json = undefined;
168
+ }
169
+ }
170
+ else if (request.jsonSchema) {
107
171
  const toolUse = data.content?.find((c) => c.type === 'tool_use');
108
172
  json = toolUse?.input;
109
173
  text = json !== undefined ? JSON.stringify(json) : '';
@@ -176,6 +176,13 @@ export interface ProviderCallRequest {
176
176
  * model would 400); non-zero budgets pass through unchanged.
177
177
  */
178
178
  supportsThinkingBudgetZero: boolean;
179
+ /**
180
+ * Whether the target model accepts a forced `tool_choice`, per the registry
181
+ * entry's `supportsForcedToolChoice` flag. When `false`, the Anthropic
182
+ * adapter gets `jsonSchema` output through `output_config.format` instead
183
+ * of a forced tool call. Other providers ignore it.
184
+ */
185
+ supportsForcedToolChoice: boolean;
179
186
  jsonSchema?: JsonSchema;
180
187
  apiKey: string;
181
188
  logger?: Logger;
package/dist/log-core.js CHANGED
@@ -6,7 +6,7 @@
6
6
  // bounded, backed-off retries and per-batch identity
7
7
  // (~/migration/research/logging-reliability-design.md): every undelivered
8
8
  // batch either lands in Axiom or ends in an onGiveUp callback — the server
9
- // logger turns those into stdout drop markers that watchdog2's daily
9
+ // logger turns those into stdout drop markers that watchdog's daily
10
10
  // ship-failure check counts. makeEvent also guards the shape of `meta`: every
11
11
  // distinct key is an Axiom column (see "Key-shape guard" below).
12
12
  export const LOG_LEVELS = ['debug', 'info', 'warn', 'error'];
package/dist/log.d.ts CHANGED
@@ -15,7 +15,7 @@ export interface LoggerOptions {
15
15
  context?: Record<string, unknown>;
16
16
  fetchFn?: typeof fetch;
17
17
  }
18
- /** Register a directly-constructed shipper (e.g. watchdog2's platform-dataset
18
+ /** Register a directly-constructed shipper (e.g. watchdog's platform-dataset
19
19
  * shipper) so flushAllLoggers and shutdown cover it too. */
20
20
  export declare function registerShipper(shipper: LogShipper): void;
21
21
  /** Flush every registered shipper. `final` is the shutdown path: time-boxed
package/dist/log.js CHANGED
@@ -19,7 +19,7 @@ export { LogShipper, makeEvent, makeEvents, serializeError, setLogShapeStrict, t
19
19
  // with() children share their root's shipper, so registering loggers leaked
20
20
  // one entry per child.
21
21
  const allShippers = new Set();
22
- /** Register a directly-constructed shipper (e.g. watchdog2's platform-dataset
22
+ /** Register a directly-constructed shipper (e.g. watchdog's platform-dataset
23
23
  * shipper) so flushAllLoggers and shutdown cover it too. */
24
24
  export function registerShipper(shipper) {
25
25
  allShippers.add(shipper);
package/dist/rum.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // Real-user monitoring: Core Web Vitals shipped to the Axiom `rum` dataset
2
- // (targets: ~/migration/research/performance-targets.md). Configured by the
2
+ // (targets: ~/plasticine-way/docs/PERFORMANCE.md). Configured by the
3
3
  // same pw-log-* meta tags as log-browser; token-less pages (local dev)
4
4
  // no-op. Metrics finalize on tab-hide, so the shipper flushes there with
5
5
  // keepalive fetch.
package/dist/s2s.js CHANGED
@@ -99,7 +99,7 @@ export async function resolveS2sCaller(opts, headers, method, path) {
99
99
  // ("Wrong number of segments in token: <jwt>", "Invalid token signature:
100
100
  // <jwt>"), so the error is never logged as-is: the token's shape and a
101
101
  // redacted message only (PW LESSONS 2026-09-02 — log the shape, never
102
- // the credential). Found by Mirror2's device-key design review.
102
+ // the credential). Found by Mirror's device-key design review.
103
103
  opts.logger?.warn('s2s token verification failed', {
104
104
  path,
105
105
  reason: redactToken(err instanceof Error ? err.message : String(err), m[1]),
@@ -55,7 +55,7 @@ export declare class TwitterApiError extends Error {
55
55
  constructor(message: string, apiCalls: number);
56
56
  }
57
57
  export declare function assertTweetId(id: string): void;
58
- /** Harvested from Reader2/Mirror2: bounded retries and timeout, four active
58
+ /** Harvested from Reader/Mirror: bounded retries and timeout, four active
59
59
  * requests per shared client. Credentials and upstream bodies are never logged. */
60
60
  export declare class TwitterApiIoClient implements TwitterClient {
61
61
  private readonly apiKey;
@@ -17,7 +17,7 @@ export function assertTweetId(id) {
17
17
  if (!/^\d{1,25}$/.test(id))
18
18
  throw new Error('Invalid tweet id');
19
19
  }
20
- /** Harvested from Reader2/Mirror2: bounded retries and timeout, four active
20
+ /** Harvested from Reader/Mirror: bounded retries and timeout, four active
21
21
  * requests per shared client. Credentials and upstream bodies are never logged. */
22
22
  export class TwitterApiIoClient {
23
23
  apiKey;
@@ -50,10 +50,10 @@ export class MirrorTweetClient {
50
50
  signal: AbortSignal.timeout(30_000),
51
51
  });
52
52
  if (!response.ok)
53
- throw new Error(`Mirror2 tweets query HTTP ${response.status}`);
53
+ throw new Error(`Mirror tweets query HTTP ${response.status}`);
54
54
  const data = await response.json();
55
55
  if (!Array.isArray(data.records))
56
- throw new Error('Mirror2 response has no records array');
56
+ throw new Error('Mirror response has no records array');
57
57
  const tweets = new Map();
58
58
  for (const r of data.records) {
59
59
  if (r.is_deleted || r.deleted_at)
package/dist/twitter.js CHANGED
@@ -67,7 +67,7 @@ export async function assembleThread(id, options) {
67
67
  add(await mirror.getTweetsByIds(missing), 'mirror2');
68
68
  }
69
69
  catch (error) {
70
- warning('Mirror2 lookup failed', error);
70
+ warning('Mirror lookup failed', error);
71
71
  }
72
72
  const absent = missing.filter((key) => !records.has(key));
73
73
  if (absent.length)
@@ -85,10 +85,10 @@ export async function assembleThread(id, options) {
85
85
  add(conversation.tweets, 'mirror2');
86
86
  archivedAuthor = conversation.complete && conversation.tweets.some((tweet) => sameAuthor(tweet, linked));
87
87
  if (!conversation.complete)
88
- warning('Mirror2 conversation reached its row limit; searching the author via API');
88
+ warning('Mirror conversation reached its row limit; searching the author via API');
89
89
  }
90
90
  catch (error) {
91
- warning('Mirror2 conversation failed', error);
91
+ warning('Mirror conversation failed', error);
92
92
  }
93
93
  if (!archivedAuthor && /^\w+$/.test(linked.author.userName)) {
94
94
  let cursor;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steve31415/baselib",
3
- "version": "3.15.0",
3
+ "version": "3.17.0",
4
4
  "description": "Plasticine new-world shared platform library: logging, auth, service-to-service auth, db, HTTP, sync, app updates",
5
5
  "type": "module",
6
6
  "license": "MIT",