@aragon-agent/cli 0.6.1 → 0.6.3
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 +216 -0
- package/README.md +241 -18
- package/dist/agent/agent-mode.d.ts +39 -0
- package/dist/agent/agent-mode.js +36 -0
- package/dist/agent/controller.d.ts +74 -1
- package/dist/agent/controller.js +200 -1
- package/dist/agent/headless.d.ts +8 -0
- package/dist/agent/headless.js +47 -0
- package/dist/agent/reducer.d.ts +120 -0
- package/dist/agent/reducer.js +201 -2
- package/dist/cli.js +297 -6
- package/dist/commands/builtins.js +73 -23
- package/dist/commands/registry.d.ts +27 -0
- package/dist/compaction/archive.d.ts +175 -0
- package/dist/compaction/archive.js +267 -0
- package/dist/compaction/child.d.ts +97 -0
- package/dist/compaction/child.js +113 -0
- package/dist/compaction/command.d.ts +41 -0
- package/dist/compaction/command.js +342 -0
- package/dist/compaction/compactor.d.ts +268 -0
- package/dist/compaction/compactor.js +689 -0
- package/dist/compaction/digest.d.ts +62 -0
- package/dist/compaction/digest.js +164 -0
- package/dist/compaction/limits.d.ts +165 -0
- package/dist/compaction/limits.js +172 -0
- package/dist/compaction/pressure.d.ts +135 -0
- package/dist/compaction/pressure.js +167 -0
- package/dist/compaction/summarize-call.d.ts +70 -0
- package/dist/compaction/summarize-call.js +86 -0
- package/dist/compaction/summary-prompt.d.ts +85 -0
- package/dist/compaction/summary-prompt.js +197 -0
- package/dist/compaction/tail-budget.d.ts +52 -0
- package/dist/compaction/tail-budget.js +67 -0
- package/dist/compaction/types.d.ts +179 -0
- package/dist/compaction/types.js +22 -0
- package/dist/compaction/wiring.d.ts +255 -0
- package/dist/compaction/wiring.js +556 -0
- package/dist/config/app-paths.d.ts +14 -0
- package/dist/config/app-paths.js +16 -0
- package/dist/config/cli-commands.d.ts +41 -0
- package/dist/config/cli-commands.js +77 -1
- package/dist/config/env.d.ts +4 -1
- package/dist/config/env.js +92 -2
- package/dist/config/load.d.ts +31 -0
- package/dist/config/load.js +63 -1
- package/dist/config/schema.d.ts +159 -0
- package/dist/config/schema.js +137 -0
- package/dist/config/store.js +17 -1
- package/dist/config/ui-state.d.ts +44 -6
- package/dist/config/ui-state.js +21 -12
- package/dist/diagnostics/doctor.js +41 -1
- package/dist/exec/events.d.ts +50 -1
- package/dist/exec/index.js +8 -4
- package/dist/exec/runner.js +44 -2
- package/dist/input/mouse-events.d.ts +31 -5
- package/dist/input/mouse-events.js +86 -27
- package/dist/input/stdin-mouse-filter.d.ts +8 -3
- package/dist/logging/install.d.ts +51 -1
- package/dist/logging/install.js +28 -0
- package/dist/logging/logger.d.ts +1 -1
- package/dist/session/cli-commands.js +23 -1
- package/dist/session/persist.js +19 -0
- package/dist/session/store.d.ts +18 -0
- package/dist/session/store.js +59 -2
- package/dist/team/report.js +7 -1
- package/dist/team/runtime.d.ts +10 -0
- package/dist/team/runtime.js +7 -0
- package/dist/team/subagent.d.ts +31 -1
- package/dist/team/subagent.js +42 -15
- package/dist/team/types.d.ts +8 -0
- package/dist/ui/ActivityLine.d.ts +19 -1
- package/dist/ui/ActivityLine.js +10 -2
- package/dist/ui/App.d.ts +48 -1
- package/dist/ui/App.js +317 -17
- package/dist/ui/Composer.d.ts +26 -1
- package/dist/ui/Composer.js +6 -6
- package/dist/ui/PromptInput.d.ts +34 -1
- package/dist/ui/PromptInput.js +54 -3
- package/dist/ui/StatusBar.d.ts +40 -0
- package/dist/ui/StatusBar.js +13 -4
- package/dist/ui/TeamPanel.js +7 -1
- package/dist/ui/Transcript.d.ts +21 -1
- package/dist/ui/Transcript.js +32 -2
- package/dist/ui/clipboard.d.ts +52 -0
- package/dist/ui/clipboard.js +91 -0
- package/dist/ui/entries/CompactionCard.d.ts +75 -0
- package/dist/ui/entries/CompactionCard.js +158 -0
- package/dist/ui/frame-differ.d.ts +46 -0
- package/dist/ui/frame-differ.js +94 -10
- package/dist/ui/gauge.d.ts +16 -1
- package/dist/ui/gauge.js +25 -6
- package/dist/ui/glyphs.d.ts +11 -0
- package/dist/ui/glyphs.js +2 -0
- package/dist/ui/layout/ScrollViewport.d.ts +43 -6
- package/dist/ui/layout/ScrollViewport.js +128 -21
- package/dist/ui/layout/budget.d.ts +10 -3
- package/dist/ui/layout/budget.js +10 -3
- package/dist/ui/layout/follow-state.d.ts +137 -0
- package/dist/ui/layout/follow-state.js +150 -0
- package/dist/ui/layout/virtual-window.d.ts +11 -0
- package/dist/ui/layout/virtual-window.js +34 -0
- package/dist/ui/overlays/HelpOverlay.js +18 -2
- package/dist/ui/overlays/SettingsScreen.d.ts +47 -1
- package/dist/ui/overlays/SettingsScreen.js +60 -1
- package/dist/ui/palettes.d.ts +16 -0
- package/dist/ui/palettes.js +6 -0
- package/dist/ui/screen.d.ts +22 -0
- package/dist/ui/screen.js +70 -8
- package/dist/ui/selection/highlight.d.ts +39 -0
- package/dist/ui/selection/highlight.js +64 -0
- package/dist/ui/selection/screen-mirror.d.ts +24 -0
- package/dist/ui/selection/screen-mirror.js +35 -0
- package/dist/ui/selection/selection-controller.d.ts +88 -0
- package/dist/ui/selection/selection-controller.js +282 -0
- package/dist/ui/selection/selection.d.ts +90 -0
- package/dist/ui/selection/selection.js +223 -0
- package/dist/ui/stdout-frame-writer.d.ts +33 -0
- package/dist/ui/stdout-frame-writer.js +15 -0
- package/dist/ui/theme.d.ts +11 -0
- package/dist/ui/theme.js +2 -0
- package/dist/ui/transcript-text.js +23 -0
- package/dist/ui/use-startup-notices.d.ts +62 -5
- package/dist/ui/use-startup-notices.js +81 -17
- package/dist/ui/use-wheel-routing.js +8 -0
- package/dist/ui/win-vt-force.d.ts +136 -0
- package/dist/ui/win-vt-force.js +237 -0
- package/package.json +4 -2
package/dist/agent/reducer.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* synthesizes a generic error notice (a swallowed throw is never a no-op).
|
|
12
12
|
*/
|
|
13
13
|
import process from 'node:process';
|
|
14
|
+
import { occupiedTokens } from '../compaction/pressure.js';
|
|
14
15
|
import { computeCost } from './usage.js';
|
|
15
16
|
import { ENTRY_LIMITS, appendBounded, entryRetain, trimEntries } from './entry-limits.js';
|
|
16
17
|
/** Default auto-dismiss window for a toast. */
|
|
@@ -46,11 +47,14 @@ export function initialViewState(seed = {}) {
|
|
|
46
47
|
todos: null,
|
|
47
48
|
retry: null,
|
|
48
49
|
fast: null,
|
|
50
|
+
compaction: null,
|
|
51
|
+
contextTokensEstimated: false,
|
|
49
52
|
seq: 0,
|
|
50
53
|
teamEntryId: undefined,
|
|
51
54
|
todoEntryId: undefined,
|
|
52
55
|
retryEntryId: undefined,
|
|
53
56
|
fastEntryId: undefined,
|
|
57
|
+
compactionEntryId: undefined,
|
|
54
58
|
streamingId: undefined,
|
|
55
59
|
turnProduced: false,
|
|
56
60
|
errorNoticed: false,
|
|
@@ -223,6 +227,17 @@ function reduceStreamEvent(se) {
|
|
|
223
227
|
return [];
|
|
224
228
|
}
|
|
225
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* The opening words of the `context_overflow` banner.
|
|
232
|
+
*
|
|
233
|
+
* A SHARED CONSTANT RATHER THAN TWO LITERALS, because `compactionStart` matches
|
|
234
|
+
* on it to rewrite the notice a successful reactive recovery would otherwise
|
|
235
|
+
* leave behind (P1-4). Two spellings that must agree, with nothing checking that
|
|
236
|
+
* they do, is how that rewrite silently stops matching — at which point the red
|
|
237
|
+
* "start over" banner comes back and only on the recovery path, which is the one
|
|
238
|
+
* place nobody re-tests.
|
|
239
|
+
*/
|
|
240
|
+
export const CONTEXT_OVERFLOW_NOTICE_PREFIX = 'Context length exceeded';
|
|
226
241
|
/** Turn a provider error into a friendly banner, mapping LLMError.errorType. */
|
|
227
242
|
export function formatStreamError(error) {
|
|
228
243
|
const errorType = error.errorType;
|
|
@@ -235,7 +250,13 @@ export function formatStreamError(error) {
|
|
|
235
250
|
case 'overloaded':
|
|
236
251
|
return `Provider overloaded - please retry shortly. (${base})`;
|
|
237
252
|
case 'context_overflow':
|
|
238
|
-
|
|
253
|
+
// `/compact` IS NAMED FIRST, and adding it is the second half of P1-4's
|
|
254
|
+
// fix. This remedy list predates context compaction and was, until it
|
|
255
|
+
// existed, complete: throwing the conversation away really was the only
|
|
256
|
+
// exit. It is not any more, and a banner that offers only `/reset` now
|
|
257
|
+
// tells the user to discard a session the very next line of code may be
|
|
258
|
+
// about to save.
|
|
259
|
+
return `${CONTEXT_OVERFLOW_NOTICE_PREFIX} - try /compact, or start over with /reset. (${base})`;
|
|
239
260
|
case 'network_error':
|
|
240
261
|
return `Network error reaching the provider. (${base})`;
|
|
241
262
|
case 'timeout':
|
|
@@ -295,6 +316,11 @@ function appendEntry(state, entries, entry) {
|
|
|
295
316
|
// `fastEntryId` IS PART OF THIS LIST for the reason the note above gives:
|
|
296
317
|
// a fifth live card left out of it is a card I-L1-1 does not cover.
|
|
297
318
|
state.fastEntryId,
|
|
319
|
+
// And `compactionEntryId` is the sixth. Same reason, and it is the one
|
|
320
|
+
// whose absence would be hardest to notice: a compaction card is rare, so
|
|
321
|
+
// a `mapEntry` that silently stopped matching would be found by a user
|
|
322
|
+
// rather than by a test.
|
|
323
|
+
state.compactionEntryId,
|
|
298
324
|
].filter((id) => typeof id === 'string');
|
|
299
325
|
for (const id of live) {
|
|
300
326
|
if (!trimmed.entries.some((e) => e.id === id)) {
|
|
@@ -584,7 +610,17 @@ export function viewReducer(state, action) {
|
|
|
584
610
|
...state,
|
|
585
611
|
streamingId: undefined,
|
|
586
612
|
turnProduced: true,
|
|
587
|
-
|
|
613
|
+
// ONE FUNCTION, SHARED WITH THE COMPACTION TRIGGER (§3.4.1 / R-11 /
|
|
614
|
+
// AC-3). The old expression — `inputTokens + outputTokens` — silently
|
|
615
|
+
// UNDER-reports for anyone behind a caching gateway, because Anthropic
|
|
616
|
+
// reports `input_tokens` EXCLUDING cached tokens and puts them in two
|
|
617
|
+
// separate fields. That was a live defect in the number this gauge shows;
|
|
618
|
+
// it becomes a much worse one the moment a trigger fires on a different
|
|
619
|
+
// number from the one the user is watching.
|
|
620
|
+
contextTokens: occupiedTokens(action.usage),
|
|
621
|
+
// A MEASUREMENT SUPERSEDES A DERIVATION (§6.2). `compactionEnd` writes an
|
|
622
|
+
// estimate so the bar falls immediately; this is where it stops being one.
|
|
623
|
+
contextTokensEstimated: false,
|
|
588
624
|
usageTotal: {
|
|
589
625
|
inputTokens: state.usageTotal.inputTokens + action.usage.inputTokens,
|
|
590
626
|
outputTokens: state.usageTotal.outputTokens + action.usage.outputTokens,
|
|
@@ -713,6 +749,12 @@ export function viewReducer(state, action) {
|
|
|
713
749
|
// holds, it is a projection of an entry that has just been erased.
|
|
714
750
|
retryEntryId: undefined,
|
|
715
751
|
retry: null,
|
|
752
|
+
// Same reasoning again for the compaction card: the entry is gone, so the
|
|
753
|
+
// id must go with it or `compactionEnd` would `mapEntry` an id that no
|
|
754
|
+
// longer exists. `compaction` STAYS — unlike `retry` it is not a
|
|
755
|
+
// projection of an entry, it is the live state of a subsystem that is
|
|
756
|
+
// still running, and `/clear` clears the screen rather than the session.
|
|
757
|
+
compactionEntryId: undefined,
|
|
716
758
|
};
|
|
717
759
|
case 'resetConversation':
|
|
718
760
|
return {
|
|
@@ -731,6 +773,11 @@ export function viewReducer(state, action) {
|
|
|
731
773
|
todoEntryId: undefined,
|
|
732
774
|
retryEntryId: undefined,
|
|
733
775
|
retry: null,
|
|
776
|
+
compactionEntryId: undefined,
|
|
777
|
+
// `contextTokens` is zeroed above, so the flag that says it is a
|
|
778
|
+
// DERIVATION has to go with it — otherwise a fresh conversation renders
|
|
779
|
+
// `~0%` and the tilde is a lie about a number that is simply zero.
|
|
780
|
+
contextTokensEstimated: false,
|
|
734
781
|
};
|
|
735
782
|
case 'setOverlay':
|
|
736
783
|
return { ...state, overlay: action.overlay };
|
|
@@ -957,6 +1004,158 @@ export function viewReducer(state, action) {
|
|
|
957
1004
|
};
|
|
958
1005
|
case 'fastTier':
|
|
959
1006
|
return { ...state, fast: action.snapshot };
|
|
1007
|
+
// --- Context compaction (context-auto-compaction §5.5) ----------------
|
|
1008
|
+
case 'compactionStart': {
|
|
1009
|
+
const { id, seq } = nextId(state);
|
|
1010
|
+
let entries = state.entries;
|
|
1011
|
+
// RESPONSIBILITY 1 — SEAL ANY LIVE `streamingId` ENTRY, defensively.
|
|
1012
|
+
// C-8's failure mode, and reachable here: `provider.ts` records that
|
|
1013
|
+
// Anthropic can deliver an error IN-STREAM, after the connection is open
|
|
1014
|
+
// and therefore after text deltas have already been emitted. An entry that
|
|
1015
|
+
// never settles pins `Transcript`'s monotonic boundary and re-renders the
|
|
1016
|
+
// tail forever.
|
|
1017
|
+
if (state.streamingId) {
|
|
1018
|
+
entries = mapEntry(entries, state.streamingId, (e) => e.kind === 'assistant' ? { ...e, streaming: false, ...sealThinkingMs(e) } : e);
|
|
1019
|
+
}
|
|
1020
|
+
// RESPONSIBILITY 2 — REWRITE A TRAILING `context_overflow` ERROR NOTICE
|
|
1021
|
+
// (P1-4). `agent-loop.ts` emits `message_update` for EVERY stream event
|
|
1022
|
+
// before the `error` branch throws, and `reduceStreamEvent`'s `case 'error'`
|
|
1023
|
+
// turns that into a `level: 'error'` notice reading "Context length
|
|
1024
|
+
// exceeded". So the HAPPY PATH of the reactive recovery would otherwise end
|
|
1025
|
+
// with a red banner telling the user to throw away the session we are one
|
|
1026
|
+
// frame from saving.
|
|
1027
|
+
//
|
|
1028
|
+
// IT REWRITES RATHER THAN REMOVES, because `<Static>` cannot un-print an
|
|
1029
|
+
// entry it has already drawn — and it matches ONLY when that notice is the
|
|
1030
|
+
// TAIL entry, so it can never touch an older, genuine error. If the
|
|
1031
|
+
// recovery then fails, the ladder's own notice or the re-thrown 400 is what
|
|
1032
|
+
// the user is left with, so neither direction lies.
|
|
1033
|
+
if (action.trigger === 'overflow') {
|
|
1034
|
+
const tail = entries[entries.length - 1];
|
|
1035
|
+
if (tail &&
|
|
1036
|
+
tail.kind === 'notice' &&
|
|
1037
|
+
tail.level === 'error' &&
|
|
1038
|
+
tail.text.startsWith(CONTEXT_OVERFLOW_NOTICE_PREFIX)) {
|
|
1039
|
+
entries = mapEntry(entries, tail.id, (e) => e.kind === 'notice'
|
|
1040
|
+
? { ...e, level: 'info', text: 'Context window exceeded - compacting and retrying.' }
|
|
1041
|
+
: e);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
const entry = {
|
|
1045
|
+
id,
|
|
1046
|
+
kind: 'compaction',
|
|
1047
|
+
index: action.index,
|
|
1048
|
+
trigger: action.trigger,
|
|
1049
|
+
mode: 'none',
|
|
1050
|
+
applied: false,
|
|
1051
|
+
messagesBefore: 0,
|
|
1052
|
+
messagesAfter: 0,
|
|
1053
|
+
tokensBefore: 0,
|
|
1054
|
+
tokensAfter: 0,
|
|
1055
|
+
model: action.model,
|
|
1056
|
+
startedAt: Date.now(),
|
|
1057
|
+
live: true,
|
|
1058
|
+
};
|
|
1059
|
+
return {
|
|
1060
|
+
...state,
|
|
1061
|
+
seq,
|
|
1062
|
+
...appendEntry(state, entries, entry),
|
|
1063
|
+
compactionEntryId: id,
|
|
1064
|
+
streamingId: undefined,
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
case 'compactionEnd': {
|
|
1068
|
+
const { record } = action;
|
|
1069
|
+
const settle = (e) => e.kind === 'compaction'
|
|
1070
|
+
? {
|
|
1071
|
+
...e,
|
|
1072
|
+
index: record.index,
|
|
1073
|
+
trigger: record.trigger,
|
|
1074
|
+
mode: record.mode,
|
|
1075
|
+
applied: record.applied,
|
|
1076
|
+
...(record.reason !== undefined ? { reason: record.reason } : {}),
|
|
1077
|
+
messagesBefore: record.messagesBefore,
|
|
1078
|
+
messagesAfter: record.messagesAfter,
|
|
1079
|
+
tokensBefore: record.tokensBefore,
|
|
1080
|
+
tokensAfter: record.tokensAfter,
|
|
1081
|
+
...(record.summary !== undefined ? { summary: record.summary } : {}),
|
|
1082
|
+
...(record.tailRelief ? { tailRelief: record.tailRelief } : {}),
|
|
1083
|
+
model: record.model,
|
|
1084
|
+
durationMs: record.durationMs,
|
|
1085
|
+
// SETTLED. `Transcript`'s boundary is monotonic, so a card that
|
|
1086
|
+
// never settles is re-rendered on every frame for the rest of the
|
|
1087
|
+
// session (C-8).
|
|
1088
|
+
live: false,
|
|
1089
|
+
}
|
|
1090
|
+
: e;
|
|
1091
|
+
// THE SECOND BRANCH IS NOT DEFENSIVE PADDING, exactly as `fastEnd`'s is
|
|
1092
|
+
// not: the IDLE `/compact` path settles a record for a card this session
|
|
1093
|
+
// may never have opened, and "it did nothing" must never be the observable
|
|
1094
|
+
// outcome (R-8 / §6.4).
|
|
1095
|
+
if (state.compactionEntryId) {
|
|
1096
|
+
return {
|
|
1097
|
+
...state,
|
|
1098
|
+
entries: mapEntry(state.entries, state.compactionEntryId, settle),
|
|
1099
|
+
compactionEntryId: undefined,
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
const { id, seq } = nextId(state);
|
|
1103
|
+
const entry = {
|
|
1104
|
+
id,
|
|
1105
|
+
kind: 'compaction',
|
|
1106
|
+
index: record.index,
|
|
1107
|
+
trigger: record.trigger,
|
|
1108
|
+
mode: record.mode,
|
|
1109
|
+
applied: record.applied,
|
|
1110
|
+
...(record.reason !== undefined ? { reason: record.reason } : {}),
|
|
1111
|
+
messagesBefore: record.messagesBefore,
|
|
1112
|
+
messagesAfter: record.messagesAfter,
|
|
1113
|
+
tokensBefore: record.tokensBefore,
|
|
1114
|
+
tokensAfter: record.tokensAfter,
|
|
1115
|
+
...(record.summary !== undefined ? { summary: record.summary } : {}),
|
|
1116
|
+
...(record.tailRelief ? { tailRelief: record.tailRelief } : {}),
|
|
1117
|
+
model: record.model,
|
|
1118
|
+
durationMs: record.durationMs,
|
|
1119
|
+
live: false,
|
|
1120
|
+
};
|
|
1121
|
+
return { ...state, seq, ...appendEntry(state, state.entries, entry) };
|
|
1122
|
+
}
|
|
1123
|
+
case 'compactionUsage':
|
|
1124
|
+
// `usageTotal` AND NOTHING ELSE, exactly as `teamUsage` and `fastUsage`. In
|
|
1125
|
+
// particular NOT `contextTokens`: that gauge shows the LEAD's context
|
|
1126
|
+
// occupancy against the lead model's window, and a summarization is a
|
|
1127
|
+
// separate conversation with a possibly different model entirely.
|
|
1128
|
+
return {
|
|
1129
|
+
...state,
|
|
1130
|
+
usageTotal: {
|
|
1131
|
+
inputTokens: state.usageTotal.inputTokens + action.usage.inputTokens,
|
|
1132
|
+
outputTokens: state.usageTotal.outputTokens + action.usage.outputTokens,
|
|
1133
|
+
costUsd: state.usageTotal.costUsd + action.costDelta,
|
|
1134
|
+
},
|
|
1135
|
+
};
|
|
1136
|
+
case 'compactionSnapshot':
|
|
1137
|
+
return { ...state, compaction: action.snapshot };
|
|
1138
|
+
/**
|
|
1139
|
+
* THE GAUGE MUST FALL IMMEDIATELY (§6.2 / AC-5).
|
|
1140
|
+
*
|
|
1141
|
+
* `contextTokens` is otherwise only written at `turnEnd`, so a compaction
|
|
1142
|
+
* that took the session from 92 % to 24 % would leave the bar at 92 % until
|
|
1143
|
+
* the next turn completes — and the user would reasonably conclude that
|
|
1144
|
+
* nothing happened. This is a small change and it is the difference between
|
|
1145
|
+
* the feature feeling real and feeling broken.
|
|
1146
|
+
*
|
|
1147
|
+
* The value arrives ALREADY CALIBRATED from `App` (§6.2 / P1-11): it replaces
|
|
1148
|
+
* a MEASUREMENT that included the tool schemas, and core's raw estimator
|
|
1149
|
+
* never counts them, so writing the raw figure would make the bar fall too
|
|
1150
|
+
* far and then visibly climb again on the next `turn_end` for no reason the
|
|
1151
|
+
* user can see.
|
|
1152
|
+
*/
|
|
1153
|
+
case 'contextTokensEstimated':
|
|
1154
|
+
return {
|
|
1155
|
+
...state,
|
|
1156
|
+
contextTokens: Math.max(0, Math.round(action.tokens)),
|
|
1157
|
+
contextTokensEstimated: true,
|
|
1158
|
+
};
|
|
960
1159
|
// --- API retry (llm-api-retry-backoff §6.4) ---------------------------
|
|
961
1160
|
case 'retryScheduled': {
|
|
962
1161
|
const snapshot = {
|