pi-openai-codex-compat 0.0.16 → 0.0.18

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
@@ -2,6 +2,45 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.0.18 - 2026-09-30
6
+
7
+ ### Fixed
8
+
9
+ - Keep Pi 0.99 running in the terminal UI. Pi 0.99's footer reads session
10
+ methods the Codex footer did not provide, so Pi exited on its first render.
11
+
12
+ ### Added
13
+
14
+ - Opt-in Responses Lite for GPT-6.1 Sol. GPT-6.1 Sol uses standard reasoning
15
+ mode even when `pro` is configured.
16
+
17
+ ### Changed
18
+
19
+ - Require Pi `>=0.99.1 <0.100.0`. Restart Pi after upgrading it.
20
+
21
+ ## 0.0.17 - 2026-09-25
22
+
23
+ ### Fixed
24
+
25
+ - Separate menu sections with blank rows, using compact spacing on short terminals.
26
+ - Align setting names on the left and values in a stable column on the right.
27
+ Render the search placeholder in muted gray instead of ordinary input text.
28
+ Keep Save and Discard hints visible in narrow terminals.
29
+ - Align settings with the Anthropic menu: Enter changes values, Ctrl+S saves and
30
+ applies drafts, and Escape discards unapplied drafts without changing active tools.
31
+ - Save only changed overrides, preserve inheritance and external edits, and
32
+ report conflicting saves without overwriting them.
33
+ - Block command-tool switches while persistent command sessions remain instead
34
+ of terminating them while browsing settings.
35
+
36
+ ### Added
37
+
38
+ - Apply to session without changing configuration files. Reopening settings shows
39
+ active session values, including after reload, restart, or resume.
40
+ Ctrl+S can save session-only changes later.
41
+ - Custom percentage editing, configuration-source and model-applicability labels,
42
+ inherited-value resets, and consistent keyboard, focus, and mouse support.
43
+
5
44
  ## 0.0.16 - 2026-09-23
6
45
 
7
46
  ### Added
package/README.md CHANGED
@@ -14,7 +14,7 @@ OpenAI Codex compatibility for [Pi](https://github.com/earendil-works/pi-mono),
14
14
  - **Dedicated Codex tool UI**: renders command tools, `apply_patch`, `image_gen.imagegen`, and `web.run` on a shared configurable surface with compact summaries and `Ctrl+O` expansion.
15
15
  - **Hosted web-search fallback**: injects native `web_search` only when `web.run` is inactive, with cached, indexed, or live modes.
16
16
  - **Native request controls**: configures Responses API text verbosity, reasoning summaries, and standard/pro reasoning mode on supported models.
17
- - **Session-local settings pane**: `/codex-settings` changes every compatibility setting for the current session; `Enter` persists and closes, `Escape` discards unsaved changes and closes, and `Ctrl+S` persists without closing.
17
+ - **Draft settings pane**: `/codex-settings` stages edits before applying them. Apply to session stores them with the session without changing configuration files. Ctrl+S saves and applies them. Escape discards only unapplied drafts.
18
18
  - **Session-aware footer**: shows the current Pi session ID on the first line and appends non-default Codex request modes to the model side of Pi's normal second line.
19
19
 
20
20
  Pi provides the Codex OAuth flow and model catalog. At session start, this package overrides the built-in `openai-codex` runtime under the same provider id so ordinary responses and remote compaction share one transport, parser, native-history store, and sticky WebSocket session.
@@ -22,7 +22,7 @@ Pi provides the Codex OAuth flow and model catalog. At session start, this packa
22
22
  ## Requirements
23
23
 
24
24
  - Node.js 22.19 or newer
25
- - Pi `>=0.87.0 <0.88.0`
25
+ - Pi `>=0.99.1 <0.100.0`
26
26
  - An OpenAI Codex login in Pi
27
27
 
28
28
  Restart Pi after upgrading its runtime. `/reload` reloads extensions but cannot
@@ -173,7 +173,56 @@ A trusted project can override it at:
173
173
  <project>/.pi/openai-codex-compat.json
174
174
  ```
175
175
 
176
- Each session inherits the effective file-backed settings. Open `/codex-settings` to make immediate session-local changes. Press `Enter` to persist and close, `Escape` to discard unsaved changes and close, or `Ctrl+S` to persist without closing. The global file is the normal save target, while an existing trusted project override remains the target for that project. After `Ctrl+S`, later unsaved changes can still be discarded back to the values from that save.
176
+ Each session inherits the effective file-backed settings. `/codex-settings`
177
+ uses the same interaction contract as `/anthropic-settings`:
178
+
179
+ - **Enter** changes the selected value or opens its editor. It never implicitly saves.
180
+ - **Space** activates a result or inserts a space when search has focus.
181
+ - **Tab / Shift+Tab** move between search, results, and action controls.
182
+ - **F1** opens scrollable details, full errors, and the exact save target.
183
+ - Edits remain drafts until explicitly applied or saved.
184
+ - **Apply to session** applies drafts without writing configuration files and stays open.
185
+ - **Ctrl+S** saves and applies changes without closing.
186
+ - **Save and close** saves and applies changes, then closes on success.
187
+ - **Escape** cancels a field editor or discards unapplied drafts and closes the main menu.
188
+ It does not undo settings already applied to the session.
189
+
190
+ Search matches labels, configuration keys, and descriptions. The percentage
191
+ editor supports presets and custom fractional values. **Use inherited value**
192
+ removes an override. This differs from **Pi default**, which explicitly disables
193
+ an inherited percentage trigger. Rows show configuration sources, environment
194
+ locks, and model applicability. Pi's remapped selection and cancel keys are respected.
195
+
196
+ The global file is the normal save target. An existing trusted project override
197
+ is the target for that project. The target stays fixed while the menu is open.
198
+ Saving changes only edited overrides, preserves unknown keys and inherited values,
199
+ and preserves non-conflicting external edits. Conflicting edits are rejected for review.
200
+ Opening or closing without changes does not create a configuration file.
201
+ Reopening shows active session values, not freshly read file values. `*` marks
202
+ an unapplied draft. `~` marks an active session override or a value that differs
203
+ from the saved configuration. Details shows the saved value when it differs.
204
+ Ctrl+S also persists changes previously applied only to the session.
205
+ Session settings are stored as extension state entries in the Pi session,
206
+ outside model context. They survive menu closure, tree navigation, extension
207
+ reload, process restart, and switching away and back. Resuming the same session
208
+ restores its settings before provider operations. A new session, fork, or clone
209
+ has a separate identity and starts from configuration files.
210
+ Pi's `--no-session` mode cannot be resumed after exit. Pi also defers creating a
211
+ new session file until its first assistant message.
212
+ Environment-controlled fields remain locked and take precedence over restored
213
+ session preferences. Their values are not copied into session settings.
214
+
215
+ Applying and saving wait for Pi to become idle. Collect pending output with `write_stdin` or
216
+ stop running command sessions through `/ps` before applying or saving a command-tool switch.
217
+ Browsing settings and discarding drafts
218
+ never terminate those sessions. Escape cancels a pending operation without discarding
219
+ the draft. Failures leave the menu open. If a save reaches disk but cannot
220
+ be applied to the session, the menu reports that distinction and Ctrl+S retries
221
+ application. Cooperating writers use a `.settings-lock` file. An interrupted
222
+ writer can leave a lock that requires review; locks are not automatically
223
+ deleted. External editors do not participate in that lock.
224
+ Session overrides retain their original conflict checks across menu reopenings.
225
+ Conflicting external edits remain protected after reload or restart.
177
226
 
178
227
  The effective settings are printed once when a TUI session starts. The footer shows the current Pi session ID alongside the working directory and optional session name. It shows `fast` and `pro` only when enabled, and shows text verbosity or reasoning summary only when they differ from their defaults.
179
228
 
@@ -220,12 +269,19 @@ Defaults:
220
269
  | `reasoningMode` | `standard`, `pro` | `standard` | Controls supported models' execution mode independently of Pi's reasoning-effort control. The default omits `reasoning.mode`; `pro` sends `reasoning.mode: "pro"`. |
221
270
 
222
271
  Responses Lite supports exactly `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`,
223
- `gpt-6-astra`, `gpt-6-sol`, and `gpt-6-luna`. Pro reasoning mode supports
224
- `gpt-5.6`, `gpt-5.6-*`, and exactly `gpt-6-astra`. GPT-6 Sol and Luna use
225
- standard reasoning mode even when `pro` is configured. Both controls remain
226
- opt-in. GPT-6 Sol and Luna require Pi 0.87.1's model catalog.
272
+ `gpt-6-astra`, `gpt-6-sol`, `gpt-6-luna`, and `gpt-6.1-sol`. Pro reasoning mode
273
+ supports `gpt-5.6`, `gpt-5.6-*`, and exactly `gpt-6-astra`. GPT-6 Sol, GPT-6
274
+ Luna, and GPT-6.1 Sol use standard reasoning mode even when `pro` is configured.
275
+ Both controls remain opt-in. GPT-6.1 Sol requires Pi 0.99.1's model catalog.
227
276
 
228
- Invalid JSON setting values are ignored and invalid JSON does not prevent Pi from starting. The settings pane never writes on ordinary changes, refuses to overwrite invalid JSON when `Enter` or `Ctrl+S` attempts to save, and retains unknown keys when saving. Project configuration is read only when the project is trusted.
277
+ As of September 30, 2026, the Codex endpoint rejects `reasoning.mode` for every
278
+ tested model, including those listed above, so `pro` requests fail there. The
279
+ setting remains for providers that accept it. Releases do not test pro mode
280
+ against `openai-codex`.
281
+
282
+ Invalid JSON setting values are ignored and invalid JSON does not prevent Pi from
283
+ starting. The settings pane reports malformed JSON instead of overwriting it.
284
+ Project configuration is read only when the project is trusted.
229
285
 
230
286
  Every setting can also be overridden for one Pi process with an environment variable:
231
287
 
@@ -533,7 +589,9 @@ The tool generates new images with `gpt-image-2` or edits up to five local/recen
533
589
  ~/.pi/agent/generated_images/<session-id>/<call-id>.png
534
590
  ```
535
591
 
536
- The active Pi agent directory replaces `~/.pi/agent` when configured differently. Turning `imageGeneration` off removes the tool immediately for the current session; `Enter` or `Ctrl+S` in `/codex-settings` persists the value.
592
+ The active Pi agent directory replaces `~/.pi/agent` when configured differently.
593
+ Saving `imageGeneration: false` through `/codex-settings` removes the tool from
594
+ the current session.
537
595
 
538
596
  The tool registers the server-reserved schema directly with a model-facing absolute-path annotation that names the supported image formats. OpenAI rejects additional schema keywords for image-count bounds and selector exclusivity, so the executor enforces those constraints before filesystem or network access. Local paths are lexically normalized before reading. A one-line system-prompt snippet and four high-signal guidelines cover normal model use. Local images are inspected with Pi's `read` tool, and generated image content is displayed and saved automatically without Codex Code Mode wrappers.
539
597
 
@@ -592,7 +650,7 @@ npm run pack:dry
592
650
  `mise run check` runs the linters, formatters, type checks, and the offline
593
651
  test suite; `mise run test` runs only the offline suite. Both tasks bind
594
652
  `PI_PACKAGE_DIR` to `node_modules/@earendil-works/pi-coding-agent`, so tests
595
- that load Pi in-process read this repository's Pi 0.87.1 resources even when a
653
+ that load Pi in-process read this repository's Pi 0.99.1 resources even when a
596
654
  global `PI_PACKAGE_DIR` points at another installation. Ordinary `pi` launches
597
655
  outside these tasks are unaffected. Offline tests also exercise
598
656
  `scripts/release.ts` with every child process mocked: they never run Git, npm,
@@ -608,11 +666,11 @@ mise run test:live:codex
608
666
  ```
609
667
 
610
668
  The task obtains the local Codex bearer token and runs the tests with
611
- `gpt-5.6-luna`, `gpt-6-sol`, and `gpt-6-luna` at medium reasoning effort.
612
- It also packs the extension and loads that archive through the shipped Pi
613
- 0.87.1 CLI. Ordinary Responses and Responses Lite tests exercise tool calls,
669
+ `gpt-5.6-luna`, `gpt-6-sol`, `gpt-6-luna`, and `gpt-6.1-sol` at medium reasoning
670
+ effort. It also packs the extension and loads that archive through the shipped Pi
671
+ 0.99.1 CLI. Ordinary Responses and Responses Lite tests exercise tool calls,
614
672
  prompt reload, native compaction, and persisted-session resume against Codex.
615
- All three models use standard reasoning mode. SDK tests verify Responses Lite
673
+ All four models use standard reasoning mode. SDK tests verify Responses Lite
616
674
  WebSocket history, prewarming, continuation, and the built-in read tool.
617
675
  CLI tests also execute the built-in read tool after resume.
618
676
  Test credentials stay in memory and child-process environments. Test sessions
@@ -626,9 +684,9 @@ The packaged-CLI test selects its archive and executable as follows:
626
684
  existing regular file that `tar` can list, and an empty, missing, directory,
627
685
  or invalid value fails the test instead of falling back to a fresh pack.
628
686
  - `PI_CODEX_CLI_PATH` selects another Pi `cli.js`; the default is this
629
- repository's Pi 0.87.1 dependency. The test asserts that the selected
630
- executable reports version 0.87.1, the only Pi version the packaged CLI test
631
- is run against. The supported range is `>=0.87.0 <0.88.0`.
687
+ repository's Pi 0.99.1 dependency. The test asserts that the selected
688
+ executable reports version 0.99.1, the only Pi version the packaged CLI test
689
+ is run against. The supported range is `>=0.99.1 <0.100.0`.
632
690
  - Each Pi child process receives `PI_PACKAGE_DIR` bound to the selected
633
691
  executable's package directory, so the runtime under test reads its own
634
692
  metadata.
@@ -59,6 +59,7 @@ export { formatBackgroundProcesses } from "./background-process-browser.ts";
59
59
  export type CommandToolsApi = Pick<ExtensionAPI, "on" | "registerCommand" | "registerTool">;
60
60
 
61
61
  export type CommandToolsController = {
62
+ hasPersistentSessions: () => boolean;
62
63
  terminateUnifiedExecSessions: () => void;
63
64
  };
64
65
 
@@ -352,6 +353,7 @@ export default function registerCommandTools(
352
353
  pi.on("session_shutdown", () => manager.terminateAll());
353
354
 
354
355
  return {
356
+ hasPersistentSessions: () => manager.activeSessionCount() > 0,
355
357
  terminateUnifiedExecSessions() {
356
358
  manager.terminateAll().catch((error: unknown) => {
357
359
  console.error("Could not terminate unified exec sessions.", error);
@@ -1,12 +1,10 @@
1
1
  import { isBoolean, isNumber, nodeErrorCode } from "./value-contracts.ts";
2
- import { randomUUID } from "node:crypto";
3
2
  import { existsSync, readFileSync } from "node:fs";
4
- import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
5
- import { basename, dirname, join } from "node:path";
3
+ import { join } from "node:path";
6
4
  import { CONFIG_DIR_NAME, getAgentDir } from "@earendil-works/pi-coding-agent";
7
5
  import type { Static } from "typebox";
8
6
  import { Value } from "typebox/value";
9
- import { isObject, type JsonRecord } from "./codex-protocol.ts";
7
+ import { isObject } from "./codex-protocol.ts";
10
8
 
11
9
  export const WEB_SEARCH_MODE_SCHEMA = {
12
10
  enum: ["disabled", "cached", "indexed", "live"],
@@ -465,55 +463,3 @@ export function withoutEnvironmentOverrides(
465
463
  }
466
464
  return result;
467
465
  }
468
-
469
- async function readWritableConfig(filePath: string): Promise<JsonRecord> {
470
- try {
471
- const value: unknown = JSON.parse(await readFile(filePath, "utf8"));
472
- if (!isObject(value)) throw new Error("the root value must be a JSON object");
473
- return value;
474
- } catch (error) {
475
- if (nodeErrorCode(error) === "ENOENT") return {};
476
- const detail = error instanceof Error ? error.message : String(error);
477
- throw new Error(`Cannot update ${filePath}: ${detail}`, { cause: error });
478
- }
479
- }
480
-
481
- /**
482
- * Merge known setting changes into the dedicated extension file. Unknown keys
483
- * are retained, and an invalid existing file is never overwritten.
484
- */
485
- export async function saveConfig(
486
- cwd: string,
487
- projectTrusted: boolean,
488
- patch: ConfigLayer,
489
- ): Promise<string> {
490
- const filePath = writableConfigPath(cwd, projectTrusted);
491
- const current = await readWritableConfig(filePath);
492
- const next = { ...current, ...patch };
493
- const directory = dirname(filePath);
494
- const temporaryPath = join(
495
- directory,
496
- `.${basename(filePath)}.${process.pid}.${randomUUID()}.tmp`,
497
- );
498
-
499
- await mkdir(directory, { recursive: true });
500
- const existingMode = await stat(filePath)
501
- .then((metadata) => metadata.mode & 0o777)
502
- .catch((error: unknown) => {
503
- if (nodeErrorCode(error) === "ENOENT") return 0o600;
504
- throw error;
505
- });
506
-
507
- try {
508
- await writeFile(temporaryPath, `${JSON.stringify(next, null, 2)}\n`, {
509
- encoding: "utf8",
510
- flag: "wx",
511
- mode: existingMode,
512
- });
513
- await rename(temporaryPath, filePath);
514
- } finally {
515
- await rm(temporaryPath, { force: true });
516
- }
517
-
518
- return filePath;
519
- }
@@ -4,6 +4,7 @@ import {
4
4
  type ExtensionContext,
5
5
  type ReadonlyFooterDataProvider,
6
6
  type SessionEntry,
7
+ type SessionManager,
7
8
  } from "@earendil-works/pi-coding-agent";
8
9
  import type { Component } from "@earendil-works/pi-tui";
9
10
  import type { CodexCompatConfig } from "./config.ts";
@@ -69,14 +70,23 @@ export function footerModel(
69
70
  return { ...model, id, reasoning: false };
70
71
  }
71
72
 
73
+ /**
74
+ * The part of Pi's `AgentSession` that `FooterComponent` reads. Pi constructs the
75
+ * footer with its own session, so the compiler cannot check this adapter against
76
+ * the footer's actual reads; the packaged TUI test renders it through Pi.
77
+ */
72
78
  interface FooterSessionAdapter {
73
79
  readonly state: {
74
80
  model: Model<Api> | undefined;
75
81
  thinkingLevel: string;
76
82
  };
83
+ /** Pi keys its cached session statistics on this identity. */
84
+ readonly model: Model<Api> | undefined;
85
+ /** Extensions cannot observe virtual-model routing, so the footer omits it. */
86
+ readonly routedModel: undefined;
77
87
  sessionManager: Pick<
78
- ExtensionContext["sessionManager"],
79
- "getEntries" | "getCwd" | "getSessionName"
88
+ SessionManager,
89
+ "getEntries" | "getEntryCount" | "getCwd" | "getLeafId" | "getSessionId" | "getSessionName"
80
90
  >;
81
91
  getContextUsage: () => ReturnType<ExtensionContext["getContextUsage"]>;
82
92
  modelRuntime: {
@@ -190,13 +200,20 @@ class CachedFooterSession implements FooterSessionAdapter {
190
200
 
191
201
  readonly sessionManager: FooterSessionAdapter["sessionManager"];
192
202
  readonly modelRuntime: FooterSessionAdapter["modelRuntime"];
203
+ readonly routedModel = undefined;
193
204
 
194
205
  constructor(ctx: FooterContext, resolveConfig: ConfigResolver) {
195
206
  this.ctx = ctx;
196
207
  this.resolveConfig = resolveConfig;
208
+ // Pi's footer caches its statistics by session id, leaf id, entry count, and
209
+ // model. The aggregated snapshot is keyed by the same session and leaf, so
210
+ // both caches refresh together.
197
211
  this.sessionManager = {
198
212
  getEntries: () => this.getSnapshot().entries,
213
+ getEntryCount: () => this.getSnapshot().entries.length,
199
214
  getCwd: () => ctx.sessionManager.getCwd(),
215
+ getLeafId: () => ctx.sessionManager.getLeafId(),
216
+ getSessionId: () => ctx.sessionManager.getSessionId(),
200
217
  getSessionName: () => this.getSnapshot().displayName,
201
218
  };
202
219
  this.modelRuntime = {
@@ -220,6 +237,10 @@ class CachedFooterSession implements FooterSessionAdapter {
220
237
  };
221
238
  }
222
239
 
240
+ get model(): Model<Api> | undefined {
241
+ return selectedRegistryModel(this.ctx);
242
+ }
243
+
223
244
  get state(): FooterSessionAdapter["state"] {
224
245
  const model = selectedRegistryModel(this.ctx);
225
246
  return {
@@ -12,7 +12,7 @@ import registerOutputLimitContinuation, {
12
12
  } from "./output-limit-continuation.ts";
13
13
  import registerRemoteCompaction, { remoteCompactionApi } from "./remote-compaction.ts";
14
14
  import registerCodexRequestOptions from "./request-options.ts";
15
- import registerCodexSettings from "./settings-pane.ts";
15
+ import registerCodexSettings, { loadSessionConfig } from "./settings-pane.ts";
16
16
  import registerCodexThreadLineage, { codexThreadLineageApi } from "./codex-thread-lineage.ts";
17
17
  import registerCodexTools, { syncCodexTools } from "./tools.ts";
18
18
 
@@ -26,8 +26,11 @@ export {
26
26
  export default function registerOpenAICodexCompat(pi: ExtensionAPI): void {
27
27
  requirePiTranscriptRuntime(piAi, piCodingAgent);
28
28
  let activeConfig: CodexCompatConfig | undefined;
29
+ let sessionContext: Parameters<typeof loadSessionConfig>[0] | undefined;
29
30
  const resolveConfig = (ctx: ConfigContext): CodexCompatConfig => {
30
- activeConfig ??= loadConfig(ctx.cwd, ctx.isProjectTrusted());
31
+ activeConfig ??= sessionContext
32
+ ? loadSessionConfig(sessionContext)
33
+ : loadConfig(ctx.cwd, ctx.isProjectTrusted());
31
34
  return activeConfig;
32
35
  };
33
36
  const resolveToolBackground = () => activeConfig?.toolBackground ?? DEFAULT_CONFIG.toolBackground;
@@ -37,7 +40,9 @@ export default function registerOpenAICodexCompat(pi: ExtensionAPI): void {
37
40
  activeConfig?.applyPatchDiagnostics ?? DEFAULT_CONFIG.applyPatchDiagnostics;
38
41
 
39
42
  pi.on("session_start", (event, ctx) => {
40
- activeConfig = loadConfig(ctx.cwd, ctx.isProjectTrusted());
43
+ sessionContext = ctx;
44
+ activeConfig = undefined;
45
+ activeConfig = resolveConfig(ctx);
41
46
  installCodexFooter(ctx, resolveConfig);
42
47
  if (event.reason !== "reload" && ctx.mode === "tui") {
43
48
  ctx.ui.notify("pi-openai-codex-compat loaded · /codex-settings", "info");
@@ -60,6 +65,7 @@ export default function registerOpenAICodexCompat(pi: ExtensionAPI): void {
60
65
  registerCodexModelPolicy(modelPolicy, resolveConfig, commandTools);
61
66
  registerCodexSettings(pi, {
62
67
  getConfig: resolveConfig,
68
+ hasPersistentSessions: commandTools.hasPersistentSessions,
63
69
  onChange(config, ctx) {
64
70
  activeConfig = config;
65
71
  codexProvider.updateSessionConfig(ctx.sessionId, config);
@@ -24,7 +24,7 @@ export const TRANSCRIPT_APIS = [
24
24
  */
25
25
  export const SESSION_APIS = ["buildSessionProjection"] as const;
26
26
 
27
- export const REQUIRED_PI = "Pi 0.87.x";
27
+ export const REQUIRED_PI = "Pi 0.99.x";
28
28
 
29
29
  /** Check the loaded host, not package metadata that PI_PACKAGE_DIR can override. */
30
30
  export function requirePiTranscriptRuntime(
@@ -12,6 +12,7 @@ const RESPONSES_LITE_MODELS: ReadonlySet<string> = new Set([
12
12
  "gpt-6-astra",
13
13
  "gpt-6-sol",
14
14
  "gpt-6-luna",
15
+ "gpt-6.1-sol",
15
16
  ]);
16
17
 
17
18
  function isHostedTool(tool: JsonRecord): boolean {