@code-yeongyu/senpi-codemode 2026.7.28-3 → 2026.7.29
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 +16 -0
- package/README.md +4 -0
- package/package.json +5 -5
- package/src/extension/eval-status.ts +44 -0
- package/src/extension/runtime-factory.ts +3 -0
- package/src/extension/session-manager.ts +39 -3
- package/src/index.ts +20 -3
- package/src/kernels/js/rewrite-imports.ts +6 -1
- package/src/tool/detached-cell-manager.ts +25 -0
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,22 @@
|
|
|
12
12
|
|
|
13
13
|
### Removed
|
|
14
14
|
|
|
15
|
+
## [2026.7.29] - 2026-07-29
|
|
16
|
+
|
|
17
|
+
### Breaking Changes
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Show every live detached eval cell in the interactive footer, using a highlighted `↗ <language> · <title>` status for one cell and a bounded packed summary for multiple cells; clear the status immediately when the final detached cell settles ([#483](https://github.com/code-yeongyu/senpi/pull/483)).
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- Route reserved `agent()`, `output()`, and `tool_schema()` bridge calls from Python and other subprocess kernels through the reserved HTTP handler instead of attempting to execute nonexistent `__agent__`, `__output__`, and `__schema__` tools; ordinary bridge tool calls remain unchanged ([#462](https://github.com/code-yeongyu/senpi/pull/462)).
|
|
28
|
+
|
|
29
|
+
### Removed
|
|
30
|
+
|
|
15
31
|
## [2026.7.28-3] - 2026-07-28
|
|
16
32
|
|
|
17
33
|
### Breaking Changes
|
package/README.md
CHANGED
|
@@ -133,6 +133,10 @@ cell keeps only its own language kernel busy. A new same-language call returns
|
|
|
133
133
|
a busy error with its cell id and output tail; calls in other languages continue
|
|
134
134
|
normally. Do not re-run the cell.
|
|
135
135
|
|
|
136
|
+
While any cell is detached, the interactive footer shows a highlighted
|
|
137
|
+
`↗ <language> · <title>` status on the extension status line (the cell id when
|
|
138
|
+
the call had no title), clearing as soon as the last detached cell settles.
|
|
139
|
+
|
|
136
140
|
Use `eval({ action: "peek", cell_id })` for its state and buffered output, or
|
|
137
141
|
`eval({ action: "stop", cell_id })` to cancel it. Python stop interrupts the
|
|
138
142
|
existing kernel and preserves variables. JavaScript stop kills and restarts its
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/senpi-codemode",
|
|
3
|
-
"version": "2026.7.
|
|
3
|
+
"version": "2026.7.29",
|
|
4
4
|
"description": "Source-only senpi extension package for codemode evaluation tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -29,15 +29,15 @@
|
|
|
29
29
|
"access": "public"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@babel/parser": "
|
|
33
|
-
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@^2026.7.
|
|
34
|
-
"typebox": "1.
|
|
32
|
+
"@babel/parser": "8.0.4",
|
|
33
|
+
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@^2026.7.29",
|
|
34
|
+
"typebox": "1.3.8"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"@code-yeongyu/senpi": "*"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@code-yeongyu/senpi": "2026.7.
|
|
40
|
+
"@code-yeongyu/senpi": "2026.7.29"
|
|
41
41
|
},
|
|
42
42
|
"keywords": [
|
|
43
43
|
"senpi",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { EvalDetachedCellStatusEntry } from "../tool/detached-cell-manager.ts";
|
|
2
|
+
|
|
3
|
+
export const EVAL_CELLS_STATUS_KEY = "eval-cells";
|
|
4
|
+
|
|
5
|
+
/** The footer shares one status line with other extensions; keep this brief. */
|
|
6
|
+
const MAX_STATUS_LENGTH = 48;
|
|
7
|
+
/** Same glyph the transcript uses for a detached cell, so the two surfaces read as one state. */
|
|
8
|
+
const DETACHED_GLYPH = "↗";
|
|
9
|
+
|
|
10
|
+
function truncateEnd(text: string, max: number): string {
|
|
11
|
+
if (text.length <= max) return text;
|
|
12
|
+
return `${text.slice(0, Math.max(0, max - 1))}…`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Fits as many whole labels as possible into the budget, folding the rest into
|
|
17
|
+
* a `+N more` counter so the detached-cell count is never truncated away.
|
|
18
|
+
*/
|
|
19
|
+
function packLabels(labels: readonly string[], budget: number): string {
|
|
20
|
+
for (let kept = labels.length; kept >= 1; kept--) {
|
|
21
|
+
const hiddenCount = labels.length - kept;
|
|
22
|
+
const tail = hiddenCount > 0 ? ` +${hiddenCount} more` : "";
|
|
23
|
+
const joined = labels.slice(0, kept).join(", ");
|
|
24
|
+
if (joined.length + tail.length <= budget) return joined + tail;
|
|
25
|
+
}
|
|
26
|
+
const tail = labels.length > 1 ? ` +${labels.length - 1} more` : "";
|
|
27
|
+
return truncateEnd(labels[0] ?? "", Math.max(1, budget - tail.length)) + tail;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function labelOf(entry: EvalDetachedCellStatusEntry): string {
|
|
31
|
+
return entry.title === undefined || entry.title.length === 0 ? entry.cellId : entry.title;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Brief footer text for the cells still running detached; undefined clears the status. */
|
|
35
|
+
export function formatEvalCellStatus(entries: readonly EvalDetachedCellStatusEntry[]): string | undefined {
|
|
36
|
+
const first = entries[0];
|
|
37
|
+
if (first === undefined) return undefined;
|
|
38
|
+
if (entries.length === 1) {
|
|
39
|
+
const head = `${DETACHED_GLYPH} ${first.language} · `;
|
|
40
|
+
return head + truncateEnd(labelOf(first), MAX_STATUS_LENGTH - head.length);
|
|
41
|
+
}
|
|
42
|
+
const head = `${DETACHED_GLYPH} eval ${entries.length}: `;
|
|
43
|
+
return head + packLabels(entries.map(labelOf), MAX_STATUS_LENGTH - head.length);
|
|
44
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@code-yeongyu/senpi";
|
|
2
2
|
import type { AgentExecuteTool } from "../bridges/agent-bridge.ts";
|
|
3
|
+
import type { EvalSchemaToolInfo } from "../bridges/schema-bridge.ts";
|
|
3
4
|
import type { CompletionRequest, CompletionResult } from "../completion/handler.ts";
|
|
4
5
|
import {
|
|
5
6
|
type CodemodeSettings,
|
|
@@ -23,6 +24,7 @@ import {
|
|
|
23
24
|
export interface CodemodeRuntimeAPI {
|
|
24
25
|
readonly executeTool: AgentExecuteTool;
|
|
25
26
|
getActiveTools(): string[];
|
|
27
|
+
getAllTools(): readonly EvalSchemaToolInfo[];
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export interface RuntimeFactoryOptions {
|
|
@@ -71,6 +73,7 @@ export async function createRuntime(
|
|
|
71
73
|
availability,
|
|
72
74
|
artifactsDir: artifacts.dir,
|
|
73
75
|
executeTool,
|
|
76
|
+
listTools: () => pi.getAllTools(),
|
|
74
77
|
complete,
|
|
75
78
|
});
|
|
76
79
|
return {
|
|
@@ -2,19 +2,28 @@ import { join } from "node:path";
|
|
|
2
2
|
import type { ExtensionContext } from "@code-yeongyu/senpi";
|
|
3
3
|
import { type BridgeServerHandle, startBridgeServer } from "../bridge/http-server.ts";
|
|
4
4
|
import type { KernelToHostMessage } from "../bridge/protocol.ts";
|
|
5
|
+
import { isReservedToolName, runReservedTool } from "../bridges/reserved-dispatch.ts";
|
|
6
|
+
import type { EvalSchemaToolInfo } from "../bridges/schema-bridge.ts";
|
|
5
7
|
import type { CompletionRequest, CompletionResult } from "../completion/handler.ts";
|
|
6
|
-
import type
|
|
8
|
+
import { type CodemodeSettings, defaultCodemodeSettings } from "../config/settings.ts";
|
|
7
9
|
import type { InterpreterAvailability } from "../interpreters/detect.ts";
|
|
8
10
|
import { JuliaKernel } from "../kernels/jl/kernel.ts";
|
|
9
11
|
import { JavaScriptKernel } from "../kernels/js/context-manager.ts";
|
|
10
12
|
import { PythonKernel } from "../kernels/py/kernel.ts";
|
|
11
13
|
import { RubyKernel } from "../kernels/rb/kernel.ts";
|
|
14
|
+
import { marshalToolResult } from "../tool/image.ts";
|
|
12
15
|
import type { EvalKernel, EvalKernelManager, EvalLanguage, ExecuteTool } from "../tool/types.ts";
|
|
13
16
|
|
|
14
17
|
export interface CodemodeSessionManager extends EvalKernelManager {
|
|
15
18
|
dispose(): Promise<void>;
|
|
16
19
|
complete(request: CompletionRequest, ctx: ExtensionContext): Promise<CompletionResult>;
|
|
17
20
|
setContext?(ctx: ExtensionContext): void;
|
|
21
|
+
bridgeEndpoint?(): BridgeEndpoint;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface BridgeEndpoint {
|
|
25
|
+
readonly port: number;
|
|
26
|
+
readonly token: string;
|
|
18
27
|
}
|
|
19
28
|
|
|
20
29
|
export interface EvalExecutionTracker {
|
|
@@ -32,6 +41,7 @@ export interface CreateCodemodeSessionManagerOptions {
|
|
|
32
41
|
/** Session-adjacent directory used for persisted eval artifacts. */
|
|
33
42
|
readonly artifactsDir?: string;
|
|
34
43
|
readonly executeTool: ExecuteTool;
|
|
44
|
+
readonly listTools?: () => readonly EvalSchemaToolInfo[];
|
|
35
45
|
readonly complete: (request: CompletionRequest, ctx: ExtensionContext) => Promise<CompletionResult>;
|
|
36
46
|
}
|
|
37
47
|
|
|
@@ -75,14 +85,34 @@ class DefaultCodemodeSessionManager implements CodemodeSessionManager {
|
|
|
75
85
|
|
|
76
86
|
async start(): Promise<void> {
|
|
77
87
|
this.#bridge = await startBridgeServer({
|
|
78
|
-
onCall: async (request) =>
|
|
79
|
-
this.#options.executeTool(request.toolName, request.args, { signal: request.signal }),
|
|
88
|
+
onCall: async (request) => await this.#call(request),
|
|
80
89
|
onEmit: async () => undefined,
|
|
81
90
|
onCompletion: async (request) =>
|
|
82
91
|
this.#options.complete({ prompt: request.prompt, opts: request.opts }, this.#contextFor(request.signal)),
|
|
83
92
|
});
|
|
84
93
|
}
|
|
85
94
|
|
|
95
|
+
// Subprocess kernels (py/rb/jl) reach the host only through this route, so reserved
|
|
96
|
+
// helper names must dispatch exactly as the in-process JS path does in tool/cell-handler.ts.
|
|
97
|
+
// Forwarding them to executeTool made agent() fail with "Unknown tool __agent__".
|
|
98
|
+
async #call(request: { toolName: string; args: unknown; callId: string; signal: AbortSignal }): Promise<unknown> {
|
|
99
|
+
if (!isReservedToolName(request.toolName)) {
|
|
100
|
+
return await this.#options.executeTool(request.toolName, request.args, { signal: request.signal });
|
|
101
|
+
}
|
|
102
|
+
const taskTools = this.#options.settings.taskTools ?? defaultCodemodeSettings.taskTools;
|
|
103
|
+
return await runReservedTool(request.toolName, {
|
|
104
|
+
callId: request.callId,
|
|
105
|
+
args: request.args,
|
|
106
|
+
executeTool: this.#options.executeTool,
|
|
107
|
+
taskToolName: taskTools.task,
|
|
108
|
+
taskOutputToolName: taskTools.output,
|
|
109
|
+
listTools: this.#options.listTools,
|
|
110
|
+
signal: request.signal,
|
|
111
|
+
emitStatus: () => {},
|
|
112
|
+
marshalToolResult,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
86
116
|
async getKernel(language: EvalLanguage, onMessage: (message: KernelToHostMessage) => void): Promise<EvalKernel> {
|
|
87
117
|
if (this.#disposePromise) throw new CodemodeSessionDisposedError();
|
|
88
118
|
// Persistent kernels are reused across cells, but each cell needs its OWN
|
|
@@ -109,6 +139,12 @@ class DefaultCodemodeSessionManager implements CodemodeSessionManager {
|
|
|
109
139
|
return await this.#options.complete(request, ctx);
|
|
110
140
|
}
|
|
111
141
|
|
|
142
|
+
bridgeEndpoint(): BridgeEndpoint {
|
|
143
|
+
const bridge = this.#bridge;
|
|
144
|
+
if (!bridge) throw new Error("codemode bridge server is not running");
|
|
145
|
+
return { port: bridge.port, token: bridge.token };
|
|
146
|
+
}
|
|
147
|
+
|
|
112
148
|
setContext(ctx: ExtensionContext): void {
|
|
113
149
|
this.#context = ctx;
|
|
114
150
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { EvalSchemaToolInfo } from "./bridges/schema-bridge.ts";
|
|
|
5
5
|
import { type CompletionRequest, type CompletionResult, createCompletionHandler } from "./completion/handler.ts";
|
|
6
6
|
import { defaultCodemodeSettings } from "./config/settings.ts";
|
|
7
7
|
import { EvalNotifier } from "./extension/eval-notifier.ts";
|
|
8
|
+
import { EVAL_CELLS_STATUS_KEY, formatEvalCellStatus } from "./extension/eval-status.ts";
|
|
8
9
|
import {
|
|
9
10
|
createExecuteTool,
|
|
10
11
|
createRuntime,
|
|
@@ -13,7 +14,7 @@ import {
|
|
|
13
14
|
} from "./extension/runtime-factory.ts";
|
|
14
15
|
import type { CodemodeSessionManager, CreateCodemodeSessionManagerOptions } from "./extension/session-manager.ts";
|
|
15
16
|
import { SessionManagerProxy } from "./extension/session-manager-proxy.ts";
|
|
16
|
-
import { EvalDetachedCellManager } from "./tool/detached-cell-manager.ts";
|
|
17
|
+
import { EvalDetachedCellManager, type EvalDetachedCellStatusEntry } from "./tool/detached-cell-manager.ts";
|
|
17
18
|
import { createEvalTool } from "./tool/eval-tool.ts";
|
|
18
19
|
import { renderEvalCall, renderEvalResult } from "./tool/render.ts";
|
|
19
20
|
|
|
@@ -58,6 +59,18 @@ export default function senpiCodemode(pi: CodemodeExtensionAPI, options: SenpiCo
|
|
|
58
59
|
getContext: () => activeContext,
|
|
59
60
|
getMode: () => "wake",
|
|
60
61
|
});
|
|
62
|
+
const showDetachedCells = (entries: readonly EvalDetachedCellStatusEntry[]): void => {
|
|
63
|
+
const ctx = activeContext;
|
|
64
|
+
if (ctx?.ui?.setStatus === undefined) return;
|
|
65
|
+
const status = formatEvalCellStatus(entries);
|
|
66
|
+
const theme = ctx.ui.theme;
|
|
67
|
+
ctx.ui.setStatus(
|
|
68
|
+
EVAL_CELLS_STATUS_KEY,
|
|
69
|
+
status === undefined || ctx.mode !== "tui" || theme === undefined
|
|
70
|
+
? status
|
|
71
|
+
: theme.bg("selectedBg", theme.fg("text", status)),
|
|
72
|
+
);
|
|
73
|
+
};
|
|
61
74
|
const registerEvalForRuntime = (
|
|
62
75
|
runtime: SessionRuntime,
|
|
63
76
|
modelId: string | undefined,
|
|
@@ -101,7 +114,7 @@ export default function senpiCodemode(pi: CodemodeExtensionAPI, options: SenpiCo
|
|
|
101
114
|
listTools: () => pi.getAllTools(),
|
|
102
115
|
complete,
|
|
103
116
|
settings: defaultCodemodeSettings,
|
|
104
|
-
cellManager: new EvalDetachedCellManager({ notifier }),
|
|
117
|
+
cellManager: new EvalDetachedCellManager({ notifier, onStatusChange: showDetachedCells }),
|
|
105
118
|
executionTracker: manager,
|
|
106
119
|
renderers,
|
|
107
120
|
hostLine: hostLine(),
|
|
@@ -126,7 +139,11 @@ export default function senpiCodemode(pi: CodemodeExtensionAPI, options: SenpiCo
|
|
|
126
139
|
if (!replaced) return;
|
|
127
140
|
notifier.reset();
|
|
128
141
|
activeContext = ctx;
|
|
129
|
-
const cellManager = new EvalDetachedCellManager({
|
|
142
|
+
const cellManager = new EvalDetachedCellManager({
|
|
143
|
+
artifactsDir: runtime.artifactsDir,
|
|
144
|
+
notifier,
|
|
145
|
+
onStatusChange: showDetachedCells,
|
|
146
|
+
});
|
|
130
147
|
activeCells = cellManager;
|
|
131
148
|
activeRuntime = runtime;
|
|
132
149
|
activeModelId = ctx.model?.id;
|
|
@@ -49,7 +49,7 @@ function parseProgram(code: string): ReturnType<typeof parse> | undefined {
|
|
|
49
49
|
allowSuperOutsideMethod: true,
|
|
50
50
|
allowUndeclaredExports: true,
|
|
51
51
|
errorRecovery: true,
|
|
52
|
-
plugins: ["typescript"
|
|
52
|
+
plugins: ["typescript"],
|
|
53
53
|
});
|
|
54
54
|
} catch (error) {
|
|
55
55
|
if (error instanceof SyntaxError) return undefined;
|
|
@@ -130,6 +130,11 @@ function rewriteImportDeclaration(node: ImportDeclaration): string {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
function dynamicImportEdit(node: AstNode): TextEdit | undefined {
|
|
133
|
+
// Babel 8 parses dynamic import() as an ImportExpression node; Babel 7 used
|
|
134
|
+
// a CallExpression with an Import callee. Handle both shapes.
|
|
135
|
+
if (node.type === "ImportExpression") {
|
|
136
|
+
return { start: node.start, end: node.start + "import".length, text: DYNAMIC_IMPORT_CALLEE };
|
|
137
|
+
}
|
|
133
138
|
if (node.type !== "CallExpression") return undefined;
|
|
134
139
|
const callee = nodeFrom(node.value.callee);
|
|
135
140
|
if (callee?.type !== "Import") return undefined;
|
|
@@ -41,9 +41,18 @@ export interface EvalDetachedCellNotifier {
|
|
|
41
41
|
notify(cells: readonly EvalDetachedCellNotification[]): void;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/** One live detached cell, as shown in the footer status line. */
|
|
45
|
+
export interface EvalDetachedCellStatusEntry {
|
|
46
|
+
readonly cellId: string;
|
|
47
|
+
readonly language: EvalLanguage;
|
|
48
|
+
readonly title?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
44
51
|
export interface EvalDetachedCellManagerOptions {
|
|
45
52
|
readonly artifactsDir?: string;
|
|
46
53
|
readonly notifier?: EvalDetachedCellNotifier;
|
|
54
|
+
/** Called with every detached cell whenever that set changes; empty clears the status. */
|
|
55
|
+
readonly onStatusChange?: (entries: readonly EvalDetachedCellStatusEntry[]) => void;
|
|
47
56
|
}
|
|
48
57
|
|
|
49
58
|
/**
|
|
@@ -56,6 +65,7 @@ export interface EvalDetachedCellManagerOptions {
|
|
|
56
65
|
export class EvalDetachedCellManager {
|
|
57
66
|
readonly #artifactsDir: string | undefined;
|
|
58
67
|
readonly #notifier: EvalDetachedCellNotifier | undefined;
|
|
68
|
+
readonly #onStatusChange: ((entries: readonly EvalDetachedCellStatusEntry[]) => void) | undefined;
|
|
59
69
|
readonly #cells = new Map<string, ManagedCell>();
|
|
60
70
|
readonly #detachedByLanguage = new Map<EvalLanguage, ManagedCell>();
|
|
61
71
|
#notificationQueue: ManagedCell[] = [];
|
|
@@ -64,6 +74,7 @@ export class EvalDetachedCellManager {
|
|
|
64
74
|
constructor(options: EvalDetachedCellManagerOptions = {}) {
|
|
65
75
|
this.#artifactsDir = options.artifactsDir;
|
|
66
76
|
this.#notifier = options.notifier;
|
|
77
|
+
this.#onStatusChange = options.onStatusChange;
|
|
67
78
|
}
|
|
68
79
|
|
|
69
80
|
create(cellId: string, input: EvalToolInput): ManagedCell {
|
|
@@ -105,6 +116,7 @@ export class EvalDetachedCellManager {
|
|
|
105
116
|
if (!cell.canDetach || !this.#transition(cell, "detached")) return false;
|
|
106
117
|
cell.wasDetached = true;
|
|
107
118
|
this.#detachedByLanguage.set(cell.input.language, cell);
|
|
119
|
+
this.#emitStatus();
|
|
108
120
|
return true;
|
|
109
121
|
}
|
|
110
122
|
|
|
@@ -164,11 +176,24 @@ export class EvalDetachedCellManager {
|
|
|
164
176
|
if (cell.wasDetached && next !== "detached") {
|
|
165
177
|
if (this.#detachedByLanguage.get(cell.input.language) === cell)
|
|
166
178
|
this.#detachedByLanguage.delete(cell.input.language);
|
|
179
|
+
this.#emitStatus();
|
|
167
180
|
this.#queueNotification(cell);
|
|
168
181
|
}
|
|
169
182
|
return true;
|
|
170
183
|
}
|
|
171
184
|
|
|
185
|
+
#emitStatus(): void {
|
|
186
|
+
const emit = this.#onStatusChange;
|
|
187
|
+
if (emit === undefined) return;
|
|
188
|
+
emit(
|
|
189
|
+
[...this.#detachedByLanguage.values()].map((cell) => ({
|
|
190
|
+
cellId: cell.cellId,
|
|
191
|
+
language: cell.input.language,
|
|
192
|
+
...(cell.input.title === undefined ? {} : { title: cell.input.title }),
|
|
193
|
+
})),
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
172
197
|
#queueNotification(cell: ManagedCell): void {
|
|
173
198
|
if (cell.notificationQueued) return;
|
|
174
199
|
cell.notificationQueued = true;
|