@oh-my-pi/pi-coding-agent 15.1.2 → 15.1.4

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 (155) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/types/async/job-manager.d.ts +3 -2
  3. package/dist/types/cli/auth-broker-cli.d.ts +25 -0
  4. package/dist/types/cli/auth-gateway-cli.d.ts +18 -0
  5. package/dist/types/cli/grievances-cli.d.ts +12 -0
  6. package/dist/types/commands/auth-broker.d.ts +54 -0
  7. package/dist/types/commands/auth-gateway.d.ts +32 -0
  8. package/dist/types/commands/grievances.d.ts +1 -1
  9. package/dist/types/commit/agentic/tools/propose-commit.d.ts +9 -1
  10. package/dist/types/commit/agentic/tools/schemas.d.ts +9 -1
  11. package/dist/types/commit/agentic/tools/split-commit.d.ts +9 -1
  12. package/dist/types/config/model-registry.d.ts +3 -0
  13. package/dist/types/config/models-config-schema.d.ts +1 -0
  14. package/dist/types/config/settings-schema.d.ts +46 -0
  15. package/dist/types/discovery/agents.d.ts +12 -1
  16. package/dist/types/edit/renderer.d.ts +3 -0
  17. package/dist/types/eval/index.d.ts +0 -2
  18. package/dist/types/goals/tools/goal-tool.d.ts +10 -2
  19. package/dist/types/index.d.ts +0 -1
  20. package/dist/types/internal-urls/index.d.ts +1 -1
  21. package/dist/types/internal-urls/{pi-protocol.d.ts → omp-protocol.d.ts} +3 -3
  22. package/dist/types/internal-urls/types.d.ts +1 -1
  23. package/dist/types/main.d.ts +11 -2
  24. package/dist/types/modes/acp/acp-agent.d.ts +2 -1
  25. package/dist/types/modes/acp/acp-event-mapper.d.ts +13 -1
  26. package/dist/types/modes/acp/acp-mode.d.ts +3 -1
  27. package/dist/types/modes/emoji-autocomplete.d.ts +16 -0
  28. package/dist/types/modes/interactive-mode.d.ts +1 -1
  29. package/dist/types/modes/prompt-action-autocomplete.d.ts +4 -0
  30. package/dist/types/plan-mode/approved-plan.d.ts +10 -4
  31. package/dist/types/sdk.d.ts +10 -3
  32. package/dist/types/session/agent-session.d.ts +7 -3
  33. package/dist/types/session/auth-broker-config.d.ts +13 -0
  34. package/dist/types/session/auth-storage.d.ts +1 -1
  35. package/dist/types/session/client-bridge.d.ts +3 -0
  36. package/dist/types/tools/eval.d.ts +41 -7
  37. package/dist/types/tools/irc.d.ts +8 -2
  38. package/dist/types/tools/report-tool-issue.d.ts +118 -1
  39. package/dist/types/tools/resolve.d.ts +8 -2
  40. package/examples/custom-tools/README.md +3 -12
  41. package/examples/extensions/README.md +2 -15
  42. package/examples/extensions/api-demo.ts +1 -7
  43. package/package.json +7 -7
  44. package/src/async/job-manager.ts +111 -13
  45. package/src/autoresearch/tools/init-experiment.ts +11 -33
  46. package/src/autoresearch/tools/log-experiment.ts +10 -24
  47. package/src/autoresearch/tools/run-experiment.ts +1 -1
  48. package/src/autoresearch/tools/update-notes.ts +2 -9
  49. package/src/cli/auth-broker-cli.ts +746 -0
  50. package/src/cli/auth-gateway-cli.ts +342 -0
  51. package/src/cli/grievances-cli.ts +109 -16
  52. package/src/cli/update-cli.ts +1 -5
  53. package/src/cli.ts +4 -2
  54. package/src/commands/auth-broker.ts +96 -0
  55. package/src/commands/auth-gateway.ts +61 -0
  56. package/src/commands/grievances.ts +13 -8
  57. package/src/commands/launch.ts +1 -1
  58. package/src/commit/agentic/agent.ts +2 -0
  59. package/src/commit/agentic/tools/analyze-file.ts +2 -2
  60. package/src/commit/agentic/tools/git-file-diff.ts +2 -2
  61. package/src/commit/agentic/tools/git-hunk.ts +3 -3
  62. package/src/commit/agentic/tools/git-overview.ts +2 -2
  63. package/src/commit/agentic/tools/propose-changelog.ts +1 -3
  64. package/src/commit/agentic/tools/recent-commits.ts +1 -1
  65. package/src/commit/agentic/tools/schemas.ts +1 -9
  66. package/src/config/model-equivalence.ts +279 -174
  67. package/src/config/model-registry.ts +37 -6
  68. package/src/config/model-resolver.ts +13 -8
  69. package/src/config/models-config-schema.ts +8 -0
  70. package/src/config/settings-schema.ts +52 -0
  71. package/src/cursor.ts +1 -1
  72. package/src/debug/log-formatting.ts +1 -1
  73. package/src/debug/log-viewer.ts +1 -1
  74. package/src/debug/profiler.ts +4 -0
  75. package/src/debug/raw-sse-buffer.ts +100 -59
  76. package/src/debug/raw-sse.ts +1 -1
  77. package/src/discovery/agents.ts +15 -4
  78. package/src/edit/modes/apply-patch.ts +1 -5
  79. package/src/edit/modes/patch.ts +5 -5
  80. package/src/edit/modes/replace.ts +5 -5
  81. package/src/edit/renderer.ts +2 -1
  82. package/src/edit/streaming.ts +1 -1
  83. package/src/eval/index.ts +0 -2
  84. package/src/eval/js/shared/runtime.ts +107 -2
  85. package/src/eval/py/kernel.ts +1 -1
  86. package/src/exa/researcher.ts +4 -4
  87. package/src/exa/search.ts +10 -22
  88. package/src/exa/websets.ts +33 -33
  89. package/src/extensibility/typebox.ts +44 -17
  90. package/src/goals/tools/goal-tool.ts +3 -3
  91. package/src/index.ts +0 -3
  92. package/src/internal-urls/docs-index.generated.ts +21 -18
  93. package/src/internal-urls/index.ts +1 -1
  94. package/src/internal-urls/{pi-protocol.ts → omp-protocol.ts} +10 -10
  95. package/src/internal-urls/router.ts +3 -3
  96. package/src/internal-urls/types.ts +1 -1
  97. package/src/lsp/types.ts +8 -11
  98. package/src/main.ts +216 -146
  99. package/src/mcp/tool-bridge.ts +3 -3
  100. package/src/modes/acp/acp-agent.ts +203 -57
  101. package/src/modes/acp/acp-client-bridge.ts +2 -1
  102. package/src/modes/acp/acp-event-mapper.ts +208 -32
  103. package/src/modes/acp/acp-mode.ts +11 -3
  104. package/src/modes/components/bash-execution.ts +1 -1
  105. package/src/modes/components/diff.ts +1 -2
  106. package/src/modes/components/eval-execution.ts +1 -1
  107. package/src/modes/components/oauth-selector.ts +38 -2
  108. package/src/modes/components/tool-execution.ts +1 -2
  109. package/src/modes/components/tree-selector.ts +26 -7
  110. package/src/modes/controllers/command-controller.ts +95 -34
  111. package/src/modes/controllers/input-controller.ts +4 -3
  112. package/src/modes/data/emojis.json +1 -0
  113. package/src/modes/emoji-autocomplete.ts +285 -0
  114. package/src/modes/interactive-mode.ts +92 -19
  115. package/src/modes/print-mode.ts +3 -3
  116. package/src/modes/prompt-action-autocomplete.ts +14 -0
  117. package/src/plan-mode/approved-plan.ts +30 -9
  118. package/src/prompts/system/system-prompt.md +1 -1
  119. package/src/prompts/system/ttsr-tool-reminder.md +5 -0
  120. package/src/prompts/tools/ask.md +4 -3
  121. package/src/prompts/tools/eval.md +25 -26
  122. package/src/prompts/tools/read.md +1 -1
  123. package/src/prompts/tools/resolve.md +1 -1
  124. package/src/prompts/tools/search.md +1 -1
  125. package/src/prompts/tools/web-search.md +1 -1
  126. package/src/sdk.ts +81 -8
  127. package/src/session/agent-session.ts +362 -131
  128. package/src/session/agent-storage.ts +7 -2
  129. package/src/session/auth-broker-config.ts +102 -0
  130. package/src/session/auth-storage.ts +7 -1
  131. package/src/session/client-bridge.ts +3 -0
  132. package/src/session/streaming-output.ts +1 -1
  133. package/src/task/types.ts +10 -35
  134. package/src/tools/bash-interactive.ts +4 -1
  135. package/src/tools/bash-pty-selection.ts +2 -2
  136. package/src/tools/browser.ts +12 -20
  137. package/src/tools/eval.ts +77 -100
  138. package/src/tools/gh.ts +21 -45
  139. package/src/tools/hindsight-recall.ts +1 -1
  140. package/src/tools/hindsight-reflect.ts +2 -2
  141. package/src/tools/hindsight-retain.ts +3 -7
  142. package/src/tools/index.ts +8 -1
  143. package/src/tools/inspect-image.ts +4 -1
  144. package/src/tools/irc.ts +4 -12
  145. package/src/tools/job.ts +3 -11
  146. package/src/tools/report-tool-issue.ts +462 -17
  147. package/src/tools/resolve.ts +2 -7
  148. package/src/tools/todo-write.ts +8 -15
  149. package/src/utils/title-generator.ts +3 -0
  150. package/src/web/search/index.ts +6 -6
  151. package/dist/types/eval/parse.d.ts +0 -28
  152. package/dist/types/eval/sniff.d.ts +0 -11
  153. package/src/eval/eval.lark +0 -36
  154. package/src/eval/parse.ts +0 -407
  155. package/src/eval/sniff.ts +0 -28
@@ -1,25 +1,22 @@
1
- Run code in a persistent kernel using codeblock cells.
1
+ Run code in a persistent kernel using a list of cells.
2
2
 
3
3
  <instruction>
4
- Each cell starts with a single header line and runs until the next header (or end of input):
4
+ Each call submits one or more cells. Cells run in array order. State persists within each language across cells **and across tool calls**.
5
5
 
6
- ```
7
- *** Cell py:"optional title" t:10s rst
8
- print("hi")
9
- ```
6
+ Cell fields:
10
7
 
11
- - **Language + title**: `<lang>:"<title>"` — {{#if py}}`py` for Python{{/if}}{{#ifAll py js}}, {{/ifAll}}{{#if js}}`js` for JavaScript{{/if}}. Title may be empty (`py:""`).
12
- - **Attributes** (optional, in this order, after the language+title):
13
- - `t:<duration>`per-cell timeout. Digits with optional `ms` / `s` / `m` units (e.g. `500ms`, `15s`, `2m`). Default 30s.
14
- - `rst` — wipe this cell's own language kernel before running.{{#ifAll py js}} Other languages are untouched.{{/ifAll}}
15
- - Anything after the header line, up to the next `*** Cell` header, is the cell's code, verbatim.
16
- - Stack multiple cells back-to-back; blank lines between cells are ignored.
8
+ - `language` — {{#if py}}`"py"` for the IPython kernel{{/if}}{{#ifAll py js}}, {{/ifAll}}{{#if js}}`"js"` for the persistent JavaScript VM{{/if}}.
9
+ - `code` — cell body, verbatim. Newlines, quotes, and indentation are JSON-encoded; no fences, no headers.
10
+ - `title` (optional) short label shown in the transcript (e.g. `"imports"`, `"load config"`).
11
+ - `timeout` (optional) per-cell timeout in seconds (1-600). Default 30.
12
+ - `reset` (optional) wipe this cell's language kernel before running.{{#ifAll py js}} Reset is per-language: a `py` cell's reset does not touch the JavaScript VM and vice versa.{{/ifAll}}
17
13
 
18
14
  **Work incrementally:**
15
+
19
16
  - One logical step per cell (imports, define, test, use).
20
17
  - Pass multiple small cells in one call.
21
18
  - Define small reusable functions for individual debugging.
22
- - Put workflow explanations in the assistant message or cell title — never inside cell code.
19
+ - Put workflow explanations in the assistant message or `title` — never inside cell code.
23
20
  {{#if py}}- Python cells run inside an IPython kernel with a live event loop. Use top-level `await` directly (e.g. `await main()`); `asyncio.run(…)` raises "cannot be called from a running event loop".{{/if}}
24
21
  **On failure:** errors identify the failing cell (e.g., "Cell 3 failed"). Resubmit only the fixed cell (or fixed cell + remaining cells).
25
22
  </instruction>
@@ -55,22 +52,24 @@ Cells render like a Jupyter notebook. `display(value)` renders non-presentable d
55
52
  </output>
56
53
 
57
54
  <caution>
58
- - In session mode, use `rst` on a cell to wipe its language's kernel before running.{{#ifAll py js}} Reset is per-language: a python cell's `rst` does not touch the JavaScript kernel and vice versa.{{/ifAll}}
59
55
  {{#if js}}- **js**: the VM exposes a selective `process` subset, Web APIs, `Buffer`, `fs/promises`, and the `Bun` global.
60
56
  {{/if}}</caution>
61
57
 
62
58
  <example>
63
- {{#if py}}*** Cell py:"imports" t:10s
64
- import json
65
- from pathlib import Path
59
+ {{#if py}}```json
60
+ {
61
+ "cells": [
62
+ { "language": "py", "title": "imports", "timeout": 10, "code": "import json\nfrom pathlib import Path" },
63
+ { "language": "py", "title": "load config", "code": "data = json.loads(read('package.json'))\ndisplay(data)" }
64
+ ]
65
+ }
66
+ ```{{/if}}{{#ifAll py js}}
66
67
 
67
- *** Cell py:"load config"
68
- data = json.loads(read('package.json'))
69
- display(data)
70
- {{/if}}{{#ifAll py js}}
71
- {{/ifAll}}{{#if js}}*** Cell js:"summary" rst
72
- const data = JSON.parse(await read('package.json'));
73
- display(data);
74
- return data.name;
75
- {{/if}}
68
+ {{/ifAll}}{{#if js}}```json
69
+ {
70
+ "cells": [
71
+ { "language": "js", "title": "summary", "reset": true, "code": "const data = JSON.parse(await read('package.json'));\ndisplay(data);\nreturn data.name;" }
72
+ ]
73
+ }
74
+ ```{{/if}}
76
75
  </example>
@@ -28,7 +28,7 @@ Append `:<sel>` to `path`. The bare path falls back to the default mode.
28
28
 
29
29
  - Reading a directory path returns a depth-limited dirent listing.
30
30
  {{#if IS_HL_MODE}}
31
- - Reading a file with an explicit selector returns lines prefixed with `line+hash` anchors: `41th|def alpha():`. The 2-char hash is a content fingerprint that `edit` / `apply_patch` consume — copy it verbatim, NEVER fabricate.
31
+ - Reading a file with an explicit selector returns lines prefixed with `line+hash` anchors: `41th|def alpha():`. The 2-char hash is a content fingerprint that `edit` / `apply_patch` consume — copy it verbatim, NEVER fabricate. The pipe character after the hash is a separator, not part of the file content.
32
32
  {{else}}
33
33
  {{#if IS_LINE_NUMBER_MODE}}
34
34
  - Reading a file with an explicit selector returns lines prefixed with line numbers: `41|def alpha():`.
@@ -2,7 +2,7 @@ Resolves a pending action by either applying or discarding it.
2
2
  - `action` is required:
3
3
  - `"apply"` persists / submits the pending action.
4
4
  - `"discard"` rejects the pending action.
5
- - `reason` is required and must briefly explain why you chose to apply or discard.
5
+ - `reason` is required: one short complete sentence explaining why, starting with a capital letter and ending with a period.
6
6
  - `extra` (optional) is free-form metadata passed to the resolving tool. Schema depends on context:
7
7
 
8
8
  Valid whenever a pending action exists — either a preview-style staging (e.g. `ast_edit`) or a long-lived approval gate.
@@ -8,7 +8,7 @@ Searches files using powerful regex matching.
8
8
 
9
9
  <output>
10
10
  {{#if IS_HL_MODE}}
11
- - Text output is anchor-prefixed: `*5th|content` (match) or ` 9x}|content` (context, leading space). The 2-char suffix is a content fingerprint.
11
+ - Text output is anchor-prefixed: `*5th|content` (match) or ` 9x}|content` (context, leading space). The 2-char suffix is a content fingerprint. The `|` before content is a separator, not part of the file content.
12
12
  {{else}}
13
13
  {{#if IS_LINE_NUMBER_MODE}}
14
14
  - Text output is line-number-prefixed
@@ -1,4 +1,4 @@
1
- Searches the web for up-to-date information beyond Claude's knowledge cutoff.
1
+ Searches the web for up-to-date information beyond knowledge cutoff.
2
2
 
3
3
  <instruction>
4
4
  - You SHOULD prefer primary sources (papers, official docs) and corroborate key claims with multiple sources
package/src/sdk.ts CHANGED
@@ -7,7 +7,13 @@ import {
7
7
  INTENT_FIELD,
8
8
  type ThinkingLevel,
9
9
  } from "@oh-my-pi/pi-agent-core";
10
- import type { CredentialDisabledEvent, Message, Model, SimpleStreamOptions } from "@oh-my-pi/pi-ai";
10
+ import {
11
+ type CredentialDisabledEvent,
12
+ type Message,
13
+ type Model,
14
+ type SimpleStreamOptions,
15
+ streamSimple,
16
+ } from "@oh-my-pi/pi-ai";
11
17
  import {
12
18
  getOpenAICodexTransportDetails,
13
19
  prewarmOpenAICodexResponses,
@@ -93,7 +99,8 @@ import {
93
99
  SecretObfuscator,
94
100
  } from "./secrets";
95
101
  import { AgentSession } from "./session/agent-session";
96
- import { AuthStorage } from "./session/auth-storage";
102
+ import { resolveAuthBrokerConfig } from "./session/auth-broker-config";
103
+ import { AuthBrokerClient, AuthStorage, RemoteAuthCredentialStore } from "./session/auth-storage";
97
104
  import { convertToLlm } from "./session/messages";
98
105
  import { SessionManager } from "./session/session-manager";
99
106
  import { closeAllConnections } from "./ssh/connection-manager";
@@ -317,13 +324,37 @@ function getDefaultAgentDir(): string {
317
324
  // Discovery Functions
318
325
 
319
326
  /**
320
- * Create an AuthStorage instance with fallback support.
321
- * Reads from primary path first, then falls back to legacy paths (.pi, .claude).
327
+ * Create an AuthStorage instance.
328
+ *
329
+ * Default: local SQLite store at `<agentDir>/agent.db`.
330
+ *
331
+ * Broker mode: when `OMP_AUTH_BROKER_URL` is set, credentials are pulled from
332
+ * a remote auth-broker over the wire. Refresh tokens never leave the broker;
333
+ * the client receives access tokens with `refresh = "__remote__"` and calls
334
+ * back into the broker through the {@link AuthStorageOptions.refreshOAuthCredential}
335
+ * override to re-mint access tokens when needed.
322
336
  */
323
337
  export async function discoverAuthStorage(agentDir: string = getDefaultAgentDir()): Promise<AuthStorage> {
338
+ const brokerConfig = await resolveAuthBrokerConfig();
339
+ if (brokerConfig) {
340
+ const client = new AuthBrokerClient({ url: brokerConfig.url, token: brokerConfig.token });
341
+ const initialResult = await client.fetchSnapshot();
342
+ if (initialResult.status !== 200) throw new Error("Auth broker returned no initial snapshot");
343
+ const store = new RemoteAuthCredentialStore({ client, initialSnapshot: initialResult.snapshot });
344
+ // Refresh + usage hooks live on RemoteAuthCredentialStore; AuthStorage
345
+ // discovers them automatically when no explicit option overrides them.
346
+ const storage = new AuthStorage(store, {
347
+ configValueResolver: resolveConfigValue,
348
+ sourceLabel: `broker ${brokerConfig.url}`,
349
+ });
350
+ await storage.reload();
351
+ return storage;
352
+ }
324
353
  const dbPath = getAgentDbPath(agentDir);
325
-
326
- const storage = await AuthStorage.create(dbPath, { configValueResolver: resolveConfigValue });
354
+ const storage = await AuthStorage.create(dbPath, {
355
+ configValueResolver: resolveConfigValue,
356
+ sourceLabel: `local ${dbPath}`,
357
+ });
327
358
  await storage.reload();
328
359
  return storage;
329
360
  }
@@ -885,6 +916,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
885
916
  thinkingLevel = logger.time("resolveThinkingLevelForModel", () =>
886
917
  resolveThinkingLevelForModel(resolvedModel, thinkingLevel),
887
918
  );
919
+ // Fire-and-forget TLS+H2 handshake to the model's host so it overlaps
920
+ // with the rest of session setup (extension/skill load, tool registry,
921
+ // system prompt build). Without this, the first `fetch(...)` pays the
922
+ // full handshake serially — 100–300 ms transcontinental for
923
+ // api.anthropic.com from a residential IP. Every mode benefits
924
+ // (interactive, print, rpc, acp).
925
+ preconnectModelHost(model.baseUrl);
888
926
  }
889
927
 
890
928
  let skills: Skill[];
@@ -1032,7 +1070,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1032
1070
  return undefined;
1033
1071
  };
1034
1072
  const toolSession: ToolSession = {
1035
- cwd,
1073
+ get cwd() {
1074
+ return sessionManager.getCwd();
1075
+ },
1036
1076
  hasUI: options.hasUI ?? false,
1037
1077
  enableLsp,
1038
1078
  get hasEditTool() {
@@ -1763,6 +1803,18 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1763
1803
  }
1764
1804
  return key;
1765
1805
  },
1806
+ streamFn: (streamModel, context, streamOptions) =>
1807
+ streamSimple(streamModel, context, {
1808
+ ...streamOptions,
1809
+ onAuthError: async (provider, oldKey, error) => {
1810
+ await modelRegistry.authStorage.invalidateCredentialMatching(provider, oldKey, streamOptions?.signal);
1811
+ logger.debug("Retrying provider request after credential invalidation", {
1812
+ provider,
1813
+ error: error instanceof Error ? error.message : String(error),
1814
+ });
1815
+ return modelRegistry.getApiKeyForProvider(provider, agent.sessionId);
1816
+ },
1817
+ }),
1766
1818
  cursorExecHandlers,
1767
1819
  transformToolCallArguments: (args, _toolName) => {
1768
1820
  let result = args;
@@ -1899,8 +1951,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1899
1951
  }
1900
1952
 
1901
1953
  // Start LSP warmup in the background so startup does not block on language server initialization.
1954
+ // Print/script invocations (`hasUI=false`) don't render the warmup status indicator AND typically
1955
+ // finish before LSP servers would have stabilized — warming them just spends CPU parsing big
1956
+ // `initialize` responses concurrently with the LLM stream consumer, jittering perceived latency.
1957
+ // Tools that need an LSP server still spin one up on demand through `getOrCreateClient`.
1902
1958
  let lspServers: CreateAgentSessionResult["lspServers"];
1903
- if (enableLsp && settings.get("lsp.diagnosticsOnWrite")) {
1959
+ if (enableLsp && options.hasUI && settings.get("lsp.diagnosticsOnWrite")) {
1904
1960
  lspServers = discoverStartupLspServers(cwd);
1905
1961
  if (lspServers.length > 0) {
1906
1962
  void (async () => {
@@ -2017,3 +2073,20 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2017
2073
  throw error;
2018
2074
  }
2019
2075
  }
2076
+
2077
+ /**
2078
+ * Best-effort preconnect to the model's API host. Bun's `fetch.preconnect`
2079
+ * primes DNS + TCP + TLS + H2 so the first real request reuses the warm
2080
+ * connection. Errors are swallowed: preconnect is an optimization, never a
2081
+ * hard dependency.
2082
+ */
2083
+ function preconnectModelHost(baseUrl: string | undefined): void {
2084
+ if (!baseUrl) return;
2085
+ const preconnect = (globalThis.fetch as typeof fetch & { preconnect?: (url: string) => void }).preconnect;
2086
+ if (typeof preconnect !== "function") return;
2087
+ try {
2088
+ preconnect(baseUrl);
2089
+ } catch {
2090
+ // Best effort.
2091
+ }
2092
+ }